Extensible provider system for supplying external assets like images, fonts, and text to Lottie animations, enabling dynamic content replacement and customization.
Protocol-based system for providing external images referenced in Lottie animations, supporting dynamic image replacement and custom image sources.
/**
* Protocol for providing images to animations
* Enables dynamic image replacement and custom image sources
*/
public protocol AnimationImageProvider {
/**
* Provide image for animation asset
* @param asset - Image asset information from animation
* @returns CGImage for the asset, nil if not available
*/
func imageForAsset(asset: ImageAsset) -> CGImage?
/**
* Provide content gravity for image asset (optional)
* @param asset - Image asset to get gravity for
* @returns Content gravity mode for layer display
*/
func contentsGravity(for asset: ImageAsset) -> CALayerContentsGravity
/**
* Whether provider results can be cached (optional)
* @returns true if results are stable and can be cached
*/
var cacheEligible: Bool { get }
}
/**
* Image asset information from animation
* Contains metadata about referenced external images
*/
public struct ImageAsset {
/** Unique identifier for the image asset */
public let id: String
/** Name of the image file */
public let name: String
/** Directory path for the image */
public let directory: String
/** Original width of the image */
public let width: Double
/** Original height of the image */
public let height: Double
}Default Implementation:
/**
* Default implementation providing optional methods
*/
extension AnimationImageProvider {
public func contentsGravity(for asset: ImageAsset) -> CALayerContentsGravity {
return .resizeAspectFill
}
public var cacheEligible: Bool {
return true
}
}Built-in image provider that loads images from application bundles, supporting different bundle sources and subdirectories.
/**
* Image provider that loads images from application bundles
* Supports main bundle, custom bundles, and subdirectories
*/
public class BundleImageProvider: AnimationImageProvider {
/** Bundle to search for images */
public let bundle: Bundle
/** Optional subdirectory within bundle */
public let searchPath: String?
/**
* Initialize with bundle and optional subdirectory
* @param bundle - Bundle to search for images (defaults to main bundle)
* @param searchPath - Optional subdirectory path within bundle
*/
public init(bundle: Bundle = Bundle.main, searchPath: String? = nil)
/**
* Load image from bundle for asset
* @param asset - Asset information from animation
* @returns CGImage loaded from bundle, nil if not found
*/
public func imageForAsset(asset: ImageAsset) -> CGImage?
public func contentsGravity(for asset: ImageAsset) -> CALayerContentsGravity
public var cacheEligible: Bool { true }
}Usage Examples:
import Lottie
import UIKit
class BundleImageProviderExamples {
func setupBundleProviders() {
// Default main bundle provider
let mainBundleProvider = BundleImageProvider()
// Custom bundle provider
guard let customBundle = Bundle(identifier: "com.example.assets") else { return }
let customBundleProvider = BundleImageProvider(bundle: customBundle)
// Subdirectory provider
let subdirectoryProvider = BundleImageProvider(
bundle: Bundle.main,
searchPath: "animations/images"
)
// Use with animation view
let animationView = LottieAnimationView(
animation: LottieAnimation.named("character"),
imageProvider: subdirectoryProvider
)
}
func createAnimationWithBundleImages() {
// Load animation that references external images
let animation = LottieAnimation.named("avatar_animation")
// Set up provider for animation images stored in bundle
let imageProvider = BundleImageProvider(
bundle: Bundle.main,
searchPath: "avatar_assets"
)
let animationView = LottieAnimationView(
animation: animation,
imageProvider: imageProvider
)
// Images will be loaded from main bundle's "avatar_assets" folder
animationView.play()
}
}Image provider that loads images from file system paths, supporting absolute paths, URL-based paths, and data URLs.
/**
* Image provider that loads images from file system paths
* Supports absolute paths, URLs, and data URL schemes
*/
public class FilepathImageProvider: AnimationImageProvider {
/** Base directory URL for relative image paths */
public let filepath: URL
/** Content gravity for rendered images */
public let contentsGravity: CALayerContentsGravity
/**
* Initialize with file path string
* @param filepath - Absolute file path to directory containing images
* @param contentsGravity - Content gravity for image rendering
*/
public init(filepath: String, contentsGravity: CALayerContentsGravity = .resize)
/**
* Initialize with file URL
* @param filepath - URL to directory containing images
* @param contentsGravity - Content gravity for image rendering
*/
public init(filepath: URL, contentsGravity: CALayerContentsGravity = .resize)
/**
* Load image from file system for asset
* Supports data URLs and standard file paths
* @param asset - Asset information from animation
* @returns CGImage loaded from file, nil if not found
*/
public func imageForAsset(asset: ImageAsset) -> CGImage?
public func contentsGravity(for asset: ImageAsset) -> CALayerContentsGravity
public var cacheEligible: Bool { true }
}Usage Examples:
import Lottie
class FilepathImageProviderExamples {
func setupFilepathProvider() {
// Provider for images in documents directory
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let documentsProvider = FilepathImageProvider(searchPath: documentsPath)
// Provider for images in specific directory
let customPath = "/Users/developer/project/assets/images"
let customProvider = FilepathImageProvider(searchPath: customPath)
// Use with animation
let animationView = LottieAnimationView(
animation: LottieAnimation.named("user_content"),
imageProvider: documentsProvider
)
}
func loadDynamicImages() {
// Load images from user-generated content directory
let userContentPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
.appendingPathComponent("user_images").path
let dynamicProvider = FilepathImageProvider(searchPath: userContentPath)
let animationView = LottieAnimationView(
animation: LottieAnimation.named("dynamic_content"),
imageProvider: dynamicProvider
)
// Animation will load images from user content directory
animationView.play()
}
}Creating custom image providers for specialized use cases like network images, generated content, or cached resources.
/**
* Example custom image provider for network images
*/
class NetworkImageProvider: AnimationImageProvider {
private var imageCache: [String: CGImage] = [:]
private let baseURL: URL
init(baseURL: URL) {
self.baseURL = baseURL
}
func imageForAsset(asset: ImageAsset) -> CGImage? {
// Check cache first
if let cachedImage = imageCache[asset.name] {
return cachedImage
}
// Load from network (simplified example)
let imageURL = baseURL.appendingPathComponent(asset.name)
// In real implementation, this would be asynchronous
if let data = try? Data(contentsOf: imageURL),
let image = CGImage.create(from: data) {
imageCache[asset.name] = image
return image
}
return nil
}
var cacheEligible: Bool { false } // Network images shouldn't be cached by Lottie
}
/**
* Example custom image provider for generated content
*/
class GeneratedImageProvider: AnimationImageProvider {
func imageForAsset(asset: ImageAsset) -> CGImage? {
// Generate image based on asset properties
return generateImage(
name: asset.name,
size: CGSize(width: asset.width, height: asset.height)
)
}
private func generateImage(name: String, size: CGSize) -> CGImage? {
// Create context
guard let context = CGContext(
data: nil,
width: Int(size.width),
height: Int(size.height),
bitsPerComponent: 8,
bytesPerRow: 0,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
) else { return nil }
// Generate content based on name
if name.contains("gradient") {
drawGradient(in: context, size: size)
} else if name.contains("pattern") {
drawPattern(in: context, size: size)
} else {
drawDefault(in: context, size: size)
}
return context.makeImage()
}
private func drawGradient(in context: CGContext, size: CGSize) {
// Implementation for gradient generation
}
private func drawPattern(in context: CGContext, size: CGSize) {
// Implementation for pattern generation
}
private func drawDefault(in context: CGContext, size: CGSize) {
// Implementation for default image generation
}
var cacheEligible: Bool { true } // Generated images can be cached
}Protocol-based system for providing custom fonts to text layers in animations, enabling dynamic font replacement and custom typography.
/**
* Protocol for providing fonts to animations
* Enables custom font replacement and dynamic typography
*/
public protocol AnimationFontProvider {
/**
* Provide font for family and size
* @param family - Font family name from animation
* @param size - Font size in points
* @returns CTFont for the specified family and size, nil to use default
*/
func fontFor(family: String, size: CGFloat) -> CTFont?
}
/**
* Default font provider implementation
* Uses system fonts as fallback for missing custom fonts
*/
public final class DefaultFontProvider: AnimationFontProvider {
/**
* Initialize default font provider
*/
public init()
/**
* Provide system font for family and size
* @param family - Font family name (ignored, uses system font)
* @param size - Font size in points
* @returns System font at specified size
*/
public func fontFor(family: String, size: CGFloat) -> CTFont? {
return CTFontCreateWithName("HelveticaNeue" as CFString, size, nil)
}
}Usage Examples:
import Lottie
import CoreText
class CustomFontProvider: AnimationFontProvider {
private let fontMapping: [String: String] = [
"BrandFont-Regular": "MyCustomFont-Regular",
"BrandFont-Bold": "MyCustomFont-Bold",
"BrandFont-Light": "MyCustomFont-Light"
]
func fontFor(family: String, size: CGFloat) -> CTFont? {
// Map animation font names to actual font names
let actualFontName = fontMapping[family] ?? family
// Try to create the requested font
if let font = CTFontCreateWithName(actualFontName as CFString, size, nil) {
return font
}
// Fallback to system font
return CTFontCreateWithName("HelveticaNeue" as CFString, size, nil)
}
}
class FontProviderExamples {
func setupCustomFonts() {
let customFontProvider = CustomFontProvider()
let animationView = LottieAnimationView(
animation: LottieAnimation.named("text_animation"),
fontProvider: customFontProvider
)
// Text in animation will use custom fonts
animationView.play()
}
func setupDynamicFonts() {
let dynamicFontProvider = DynamicFontProvider()
let animationView = LottieAnimationView(
animation: LottieAnimation.named("responsive_text"),
fontProvider: dynamicFontProvider
)
// Fonts will adapt to accessibility settings
animationView.play()
}
}
class DynamicFontProvider: AnimationFontProvider {
func fontFor(family: String, size: CGFloat) -> CTFont? {
// Adapt font size based on accessibility settings
let preferredSize = UIFontMetrics.default.scaledValue(for: size)
// Use preferred content size category
let fontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body)
let font = CTFontCreateWithFontDescriptor(fontDescriptor, preferredSize, nil)
return font
}
}Protocol-based system for providing dynamic text replacement in animations, enabling localization and runtime text modification.
/**
* Protocol for providing dynamic text to animations
* Enables text replacement, localization, and dynamic content
*/
public protocol AnimationKeypathTextProvider {
/**
* Provide text for keypath and source text
* @param keypath - Animation keypath for text element
* @param sourceText - Original text from animation (may be nil)
* @returns Replacement text, nil to use original text
*/
func text(for keypath: AnimationKeypath, sourceText: String?) -> String?
}
/**
* Default text provider that uses original animation text
*/
public final class DefaultTextProvider: AnimationKeypathTextProvider {
public init()
/**
* Return original text without modification
* @param keypath - Animation keypath (ignored)
* @param sourceText - Original text from animation
* @returns Original text unchanged
*/
public func text(for keypath: AnimationKeypath, sourceText: String?) -> String? {
return sourceText
}
}
/**
* Dictionary-based text provider for simple text replacement
*/
public final class DictionaryTextProvider: AnimationKeypathTextProvider {
private let textMap: [String: String]
/**
* Initialize with text mapping dictionary
* @param textMap - Dictionary mapping keypaths to replacement text
*/
public init(_ textMap: [String: String])
/**
* Provide text based on keypath lookup
* @param keypath - Animation keypath to look up
* @param sourceText - Original text (used as fallback)
* @returns Mapped text or original text if no mapping found
*/
public func text(for keypath: AnimationKeypath, sourceText: String?) -> String? {
return textMap[keypath.string] ?? sourceText
}
}Usage Examples:
import Lottie
class TextProviderExamples {
func setupDictionaryTextProvider() {
let textMapping = [
"welcome.title": "Welcome!",
"welcome.subtitle": "Get started with our app",
"button.text": "Continue",
"footer.copyright": "© 2024 My Company"
]
let textProvider = DictionaryTextProvider(textMapping)
let animationView = LottieAnimationView(
animation: LottieAnimation.named("welcome_screen"),
textProvider: textProvider
)
// Text elements will be replaced with mapped values
animationView.play()
}
func setupLocalizationProvider() {
let localizationProvider = LocalizationTextProvider()
let animationView = LottieAnimationView(
animation: LottieAnimation.named("localized_content"),
textProvider: localizationProvider
)
// Text will be localized based on current locale
animationView.play()
}
}
/**
* Custom text provider for localization
*/
class LocalizationTextProvider: AnimationKeypathTextProvider {
func text(for keypath: AnimationKeypath, sourceText: String?) -> String? {
// Use keypath as localization key
let localizedText = NSLocalizedString(keypath.string, comment: "Animation text")
// Return localized text if different from key, otherwise use source
return localizedText != keypath.string ? localizedText : sourceText
}
}
/**
* Dynamic text provider with real-time data
*/
class DynamicTextProvider: AnimationKeypathTextProvider {
private let dataSource: DataSource
init(dataSource: DataSource) {
self.dataSource = dataSource
}
func text(for keypath: AnimationKeypath, sourceText: String?) -> String? {
// Provide dynamic text based on keypath and current data
switch keypath.string {
case "user.name":
return dataSource.currentUser?.name ?? sourceText
case "stats.count":
return "\(dataSource.currentCount)"
case "time.display":
return DateFormatter.shortTime.string(from: Date())
default:
return sourceText
}
}
}
protocol DataSource {
var currentUser: User? { get }
var currentCount: Int { get }
}
struct User {
let name: String
}
extension DateFormatter {
static let shortTime: DateFormatter = {
let formatter = DateFormatter()
formatter.timeStyle = .short
return formatter
}()
}Advanced patterns for coordinating multiple providers and handling provider updates during animation playback.
/**
* Coordinated provider system for complex animations
*/
class CoordinatedProviderSystem {
let imageProvider: AnimationImageProvider
let fontProvider: AnimationFontProvider
let textProvider: AnimationKeypathTextProvider
init(
imageProvider: AnimationImageProvider,
fontProvider: AnimationFontProvider,
textProvider: AnimationKeypathTextProvider
) {
self.imageProvider = imageProvider
self.fontProvider = fontProvider
self.textProvider = textProvider
}
func setupAnimationView() -> LottieAnimationView {
return LottieAnimationView(
animation: LottieAnimation.named("complex_animation"),
imageProvider: imageProvider,
textProvider: textProvider,
fontProvider: fontProvider
)
}
}
/**
* Provider that adapts to user preferences and system settings
*/
class AdaptiveProviderSystem {
func createAdaptiveProviders() -> (AnimationImageProvider, AnimationFontProvider, AnimationKeypathTextProvider) {
// Adapt to user preferences
let userPreferences = UserDefaults.standard
// Image provider based on quality preference
let imageProvider: AnimationImageProvider
if userPreferences.bool(forKey: "highQualityImages") {
imageProvider = BundleImageProvider(bundle: Bundle.main, searchPath: "hd_images")
} else {
imageProvider = BundleImageProvider(bundle: Bundle.main, searchPath: "sd_images")
}
// Font provider based on accessibility
let fontProvider = AccessibilityFontProvider()
// Text provider based on locale
let textProvider = LocaleAwareTextProvider()
return (imageProvider, fontProvider, textProvider)
}
}
class AccessibilityFontProvider: AnimationFontProvider {
func fontFor(family: String, size: CGFloat) -> CTFont? {
// Respect Dynamic Type and accessibility settings
let metrics = UIFontMetrics.default
let adjustedSize = metrics.scaledValue(for: size)
// Use accessibility-friendly fonts when bold text is enabled
if UIAccessibility.isBoldTextEnabled {
let boldFontName = family + "-Bold"
return CTFontCreateWithName(boldFontName as CFString, adjustedSize, nil)
}
return CTFontCreateWithName(family as CFString, adjustedSize, nil)
}
}
class LocaleAwareTextProvider: AnimationKeypathTextProvider {
func text(for keypath: AnimationKeypath, sourceText: String?) -> String? {
// Get localized text for current locale
let bundle = Bundle.main
let localizedText = bundle.localizedString(forKey: keypath.string, value: sourceText, table: "Animations")
// Apply locale-specific formatting if needed
return formatForLocale(localizedText)
}
private func formatForLocale(_ text: String?) -> String? {
guard let text = text else { return nil }
// Apply RTL adjustments, number formatting, etc.
let locale = Locale.current
if locale.characterDirection == .rightToLeft {
// Apply RTL-specific adjustments
}
return text
}
}Usage Examples:
import Lottie
class AdvancedProviderExamples {
func setupAdaptiveAnimation() {
let adaptiveSystem = AdaptiveProviderSystem()
let (imageProvider, fontProvider, textProvider) = adaptiveSystem.createAdaptiveProviders()
let animationView = LottieAnimationView(
animation: LottieAnimation.named("adaptive_content"),
imageProvider: imageProvider,
textProvider: textProvider,
fontProvider: fontProvider
)
// Animation will adapt to user preferences and system settings
animationView.play()
}
func updateProvidersAtRuntime() {
let animationView = LottieAnimationView()
// Update providers based on runtime conditions
if shouldUseHighQualityAssets() {
animationView.imageProvider = BundleImageProvider(searchPath: "hd_assets")
} else {
animationView.imageProvider = BundleImageProvider(searchPath: "sd_assets")
}
// Update text for different user states
if isUserLoggedIn() {
let userTextProvider = DictionaryTextProvider([
"greeting": "Welcome back, \(getCurrentUserName())!",
"action": "Continue"
])
animationView.textProvider = userTextProvider
} else {
let guestTextProvider = DictionaryTextProvider([
"greeting": "Welcome!",
"action": "Sign In"
])
animationView.textProvider = guestTextProvider
}
}
func shouldUseHighQualityAssets() -> Bool {
// Check network conditions, battery level, device capabilities
return true
}
func isUserLoggedIn() -> Bool {
return false
}
func getCurrentUserName() -> String {
return "User"
}
}