Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making Lottie work with macOS and AppKit (without UIKit)

Works on Xcode 13.0 and MacOS 12.0

Was stuck and was unable to make Lottie work on an macOS native app to load the animation since the Lottie framework to link it to swiftUI on all the tutorial that I have looked online require the use of UIViewRepresentable, which is not available on a macOS only app. So after some work and tinkering, finally made it work using NSViewPresentable

struct LottieView: NSViewRepresentable {    


//To allow the JSON file to be read and to display the animaton
//Allows link to SwiftUI from NSView()
let animationView = AnimationView()
var filename : String //The name of the file to be  loaded
var speed: Double //The speed at which the animation should be played
var loop: LottieLoopMode //Whever the animation should loop


var heightView: Double
var widthView: Double

func makeNSView(context: NSViewRepresentableContext<LottieView>) -> NSView {
    let view = NSView()
    let animation = Animation.named(filename) //Loads the animation
    animationView.animation = animation //Sets the animation
    animationView.animationSpeed = CGFloat(speed) //Speed
    animationView.contentMode = .scaleAspectFit //Aspect Ratio
    animationView.loopMode = loop //Whever to loop
    animationView.play() //Plays the animation
    
        animationView
        .translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(animationView)
    view.setFrameSize(CGSize(width: 100, height: 100))
    
    NSLayoutConstraint.activate([

        
        
        animationView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        animationView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        animationView.heightAnchor.constraint(equalToConstant: heightView),
        animationView.widthAnchor.constraint(equalToConstant: widthView)
    
        

    ])
    
    
    
    
    
    
    
    
   
    
    return view //Necessary in order to conform to UIVieewRepresentable
    
}

func updateNSView(_ uiView: NSView, context: NSViewRepresentableContext<LottieView> ) {
    
    
 }
}

**the only thing is that you have also to frame the view with the same width and height parameter passed to the NSViewRepresntable such that:

LottieView(filename: "cloudSyncAnimation", speed: 1.0, loop: .loop, heightView: 100, widthView: 100).frame(width: 100, height: 100, alignment: .center)

Hopefully someone can find this solution useful, probably not the most simple or elegant solution but works and I am fairly new to swift so any contribution would be appreciated

like image 415
TheFrenchGuy Avatar asked Oct 19 '25 10:10

TheFrenchGuy


1 Answers

I was having the same problem with the size of the animation. My guess was that the animationView was trying to first take the size of the original animation, so I removed the content resistance priority.

This is my implementation:

public struct LottieView: NSViewRepresentable{
    
    
    public init(lottieFile: String, loopMode: LottieLoopMode = .loop, autostart: Bool = true, contentMode: LottieContentMode = LottieContentMode.scaleAspectFit) {
        
        self.lottieFile = lottieFile
        self.loopMode = loopMode
        self.autostart = autostart
        self.contentMode = contentMode
    }
    
    
    let lottieFile: String
    let loopMode: LottieLoopMode
    let autostart: Bool
    let contentMode: LottieContentMode
    
    
    let animationView = AnimationView()
    
    public func makeNSView(context: Context) -> some NSView {
        let theView = NSView()
      
        animationView.animation = Animation.named(lottieFile)
        animationView.contentMode = .scaleAspectFit
        animationView.loopMode = loopMode
        animationView.backgroundBehavior = .pauseAndRestore
        

        if self.autostart{
            animationView.play()
        }
        
        theView.addSubview(animationView)
        
        animationView.translatesAutoresizingMaskIntoConstraints = false
        animationView.heightAnchor.constraint(equalTo: theView.heightAnchor).isActive = true
        animationView.widthAnchor.constraint(equalTo: theView.widthAnchor).isActive = true
        animationView.leadingAnchor.constraint(equalTo: theView.leadingAnchor).isActive = true
        animationView.trailingAnchor.constraint(equalTo: theView.trailingAnchor).isActive = true
        
        animationView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
        animationView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        
        return theView
    }
    
    
    func pause(){
        animationView.pause()
    }
    
    func play(){
        animationView.play()
    }
    
    func stop(){
        animationView.stop()
    }
    
    public func updateNSView(_ nsView: NSViewType, context: Context) {
        
    }
}

No need to have a wrapper or set a fixed size.

like image 191
the Reverend Avatar answered Oct 21 '25 15:10

the Reverend