Lottie-ios: SwiftUI does not respect scaling

Created on 1 Oct 2019  路  2Comments  路  Source: airbnb/lottie-ios

Check these before submitting:

  • [] The issue doesn't involve an Unsupported Feature
  • [] This issue isn't related to another open issue

This issue is a:

  • [X] Non-Crashing Bug (Visual or otherwise)
  • [] Crashing Bug
  • [] Feature Request
  • [] Regression (Something that once worked, but doesn't work anymore)

Which Version of Lottie are you using?

Lottie 3.0

What Platform are you on?

  • [] MacOS
  • [X] iOS

What Language are you in?

  • [X] Swift
  • [] Objective-C

Expected Behavior

When wrapping an AnimationView inside an UIViewRepresentable to interface with SwiftUI it is expected that the frame size is applied correctly to AnimationView.

Actual Behavior

The scale of the AnimationView is based on the animation bounds and gets overwritten.
This is because SwiftUI prefers the intrinsicContentSize over any specified frames.

Code Example

Temporary workaround is modifying lottie-ios in AnimationView.swift to report back the frame and not the animation bounds:

  // MARK: - Public (UIView Overrides)

  override public var intrinsicContentSize: CGSize {
    if let animation = animation {
      return self.frame.size
   //   return animation.bounds.size
    }
    return .zero
  }

Example:

struct ContentView: View {
  @EnvironmentObject var appContext: AppContext

    var body: some View {
      VStack {
        VStack {
          AnimationLottieView(animation: "door").aspectRatio(contentMode: .fit).frame(minWidth: 0, maxWidth: 20, minHeight:0, maxHeight: 100)
        Spacer()
      }
    }
}

AnimationView in UIViewRepresentable:

import Lottie
import SwiftUI


struct AnimationLottieView: UIViewRepresentable {
  var animation: String!
  let animationView = AnimationView()

  func makeCoordinator() -> Coordinator {
    Coordinator(self)
  }

  func makeUIView(context: UIViewRepresentableContext<AnimationLottieView>) -> UIView {
    let animationLoop = Animation.named(animation)
    animationView.animation = animationLoop
    animationView.loopMode = .loop
    animationView.contentMode = .scaleAspectFit
    animationView.play()
    return animationView
  }

  func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<AnimationLottieView>) {
  // swiftlint:disable:next force_cast
    let animView = uiView as! AnimationView
  }

  class Coordinator: NSObject {
      var parent: AnimationLottieView

      init(_ animationView: AnimationLottieView) {
        self.parent = animationView
        super.init()
      }
    }
  }

Animation JSON

Most helpful comment

I had the same problem but following solution did work for me without the need to change Lottie's code.

I created a container view and constrained the AnimationView to the container via Auto Layout. Hope that helps.

struct LottieView: UIViewRepresentable {

    var name: String!

    func makeUIView(context: UIViewRepresentableContext<LottieView>) -> UIView {
        let view = UIView()

        let animationView = AnimationView()
        animationView.animation = Animation.named(name)
        animationView.contentMode = .scaleAspectFit

        animationView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(animationView)

        NSLayoutConstraint.activate([
            animationView.widthAnchor.constraint(equalTo: view.widthAnchor),
            animationView.heightAnchor.constraint(equalTo: view.heightAnchor)
        ])

        return view
    }

    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<LottieView>) {
    }
}

All 2 comments

I had the same problem but following solution did work for me without the need to change Lottie's code.

I created a container view and constrained the AnimationView to the container via Auto Layout. Hope that helps.

struct LottieView: UIViewRepresentable {

    var name: String!

    func makeUIView(context: UIViewRepresentableContext<LottieView>) -> UIView {
        let view = UIView()

        let animationView = AnimationView()
        animationView.animation = Animation.named(name)
        animationView.contentMode = .scaleAspectFit

        animationView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(animationView)

        NSLayoutConstraint.activate([
            animationView.widthAnchor.constraint(equalTo: view.widthAnchor),
            animationView.heightAnchor.constraint(equalTo: view.heightAnchor)
        ])

        return view
    }

    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<LottieView>) {
    }
}

@reversepanda while that workaround does solve the problem, it isn't a real solution and hence makes the interface of the UIViewRepresentable incorrect. If you actually need to implement updateUIView to interact with the LottieView, you now need to iterate over uiView.subviews to find the LottieView instead of being able to directly interact with it.
Moreover, UIViewType should be LottieView, not UIView.

For this reason, the fix should be done inside Lottie instead of requiring a workaround outside of Lottie.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

loganblevins picture loganblevins  路  3Comments

Mushrankhan picture Mushrankhan  路  4Comments

Ushaka picture Ushaka  路  5Comments

muraterd picture muraterd  路  5Comments

CentrumGuy picture CentrumGuy  路  4Comments