rive-app / rive-ios

iOS runtime for Rive
MIT License
516 stars 58 forks source link

Not possible to get a natural animation size #323

Open neon8bit opened 5 months ago

neon8bit commented 5 months ago

Description

In our app we would like to know a natural size of the rive animation that we download from an URL. This is needed to layout a RiveView a certain way and also constrain other views to it. More precisely, we need to know the width/height ratio of the initial animation so that we can resize a RiveView to fit exactly its content (e.g. set fixed width to a RiveView and let the height be dynamically calculated based on width/height ratio of the animation). Unfortunately, we didn't find a way to implement it with rive.

Could you please give us an idea of how we could possibly get the animation's size data? Or if there's no such way yet, could you please implement this in Rive SDK in future releases?

Provide a Repro

import RiveRuntime
import UIKit

class ViewController: UIViewController, RiveFileDelegate {

    var riveViewModel: RiveViewModel?
    var riveView: RiveView?

    override func viewDidLoad() {
        super.viewDidLoad()
        let animationURL = "https://cdn.rive.app/animations/truck.riv"
        let riveFile = RiveFile(httpUrl: animationURL, loadCdn: true, with: self)
    }

    func riveFileDidLoad(_ riveFile: RiveFile) throws {
        let riveModel = RiveModel(riveFile: riveFile)
        self.riveViewModel = RiveViewModel(riveModel)
        self.riveView = self.riveViewModel?.createRiveView()

        // here we'd like to have some way to get a natural animation size:
        let animationSize: CGSize = riveViewModel.size 
        let ratio = animationSize.width / animationSize.height

        guard let riveView = self.riveView else { return }

        riveView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(riveView)
        NSLayoutConstraint.activate([
            riveView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            riveView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            riveView.centerYAnchor.constraint(equalTo: view.centerYAnchor),

            // so here we would be able to use the ratio for constraining the riveView
            riveView.heightAnchor.constraint(equalTo: riveView.widthAnchor, multiplier: ratio)
        ])
    }
}

Source .riv/.rev file

In the repro a sample animation URL is used: https://cdn.rive.app/animations/truck.riv

Expected behavior

We would like to have the ability to get a natural size of the rive animation that we download from an URL.

Screenshots

We also noticed, that a RiveView is not being displayed if not all the constraints are externally provided for it. So, it's not being drawn based on intrinsic size. Intrinsic size always remains CGSize(width: -1.0, height: -1.0).

image

Device & Versions (please complete the following information)

Additional context

We already used simple images (UIImage) and Lottie animations in the same context, and haven't had any similar problems. For example, Lottie provides a property LottieAnimation.size that returns a natural animation size. And for UIImage the following snippet also works fine with UIImage.size property:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let imageView = UIImageView()
        imageView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(imageView)
        NSLayoutConstraint.activate([
            imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        ])

        DispatchQueue.global().async {
            let sampleURL = URL(string: "https://fastly.picsum.photos/id/392/200/300.jpg?hmac=tcnub3WKREnSOdoCn7rQtfZkHXNWn5fXwNpHrv0o-5k")!
            let data = try! Data(contentsOf: sampleURL)
            DispatchQueue.main.async {
                let image = UIImage(data: data)!
                let ratio = image.size.height / image.size.width
                imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: ratio).isActive = true
                imageView.image = image
            }
        }
    }
}
chbeer commented 2 months ago

I was able to create a hack to get aspectRatio from the RiveModel by subclassing RiveViewModel:

class MyRiveViewModel: RiveViewModel {

    @Published var aspectRatio: CGFloat = 1

    override func riveFileDidLoad(_ riveFile: RiveFile) throws {
        try super.riveFileDidLoad(riveFile)

        if let bounds = riveModel?.artboard.bounds() {
            aspectRatio = bounds.width / bounds.height
        }
    }

}

Then I was able to use that aspect ratio on the view:

    @StateObject var riveViewModel: MyRiveViewModel

    var body: some View {
        riveViewModel.view()
            .aspectRatio(riveViewModel.aspectRatio, contentMode: .fit)
            .frame(width: geoSize.width)
    }
e-hartig commented 1 month ago

The above didn't work for me, maybe because I was using a local asset, but using the same logic in a getAspectRatio function worked fine to pull the aspect ratio on demand. Also, the artboard is a public property, so the override is not necessary, just cleaner.

class CustomRiveViewModel: RiveViewModel {
    func getAspectRatio() -> CGFloat {
        if let bounds = riveModel?.artboard.bounds() {
            return bounds.width / bounds.height
        }
        return 1.0
    }
}