SwiftUI spring timing doesn't appear to match UIKit spring timing

Viewed 73

With identical parameters, the SwiftUI spring timing function doesn't appear to match the UIKit spring timing function (specifically, UIViewPropertyAnimator).

In the video below, we have two labels, one in SwiftUI and one in UIKit, with positions being driven by spring functions with identical parameters, but the computed positions don't line up throughout the animation. What am I missing here?

Video

enter image description here

Code

import SwiftUI
import UIKit

class SwiftUIViewModel: ObservableObject {
    @Published var position: CGPoint = .zero
}

struct SwiftUIView: View {
    @ObservedObject var model: SwiftUIViewModel
    
    var body: some View {
        GeometryReader { geo in
            Text("Hello")
                .font(.system(size: 100))
                .foregroundColor(.blue)
                .position(model.position)
                .animation(.interpolatingSpring(mass: 1, stiffness: 39.47841760435743, damping: 12.566370614359172, initialVelocity: .zero))
        }
    }
}

class UIKitView: UIView {
    let referenceViewModel = SwiftUIViewModel()
    let referenceView: UIHostingController<SwiftUIView>
    
    let label = UILabel()
    
    var animator: UIViewPropertyAnimator?
    
    let tapGesture = UITapGestureRecognizer()
    
    var previousBounds: CGRect = .zero

    override func layoutSubviews() {
        guard bounds != previousBounds else {
            return
        }
        
        previousBounds = bounds
        
        label.sizeToFit()
        label.center = .init(x: bounds.width / 2, y: bounds.height / 2)
        
        referenceView.view.frame = bounds
    }
    
    init() {
        referenceView = UIHostingController(rootView: SwiftUIView(model: referenceViewModel))
        
        super.init(frame: .zero)
        
        tapGesture.addTarget(self, action: #selector(tapped))
        addGestureRecognizer(tapGesture)
        
        addSubview(referenceView.view)
        
        label.text = "Hello"
        label.textColor = .black
        label.adjustsFontSizeToFitWidth = true
        label.font = .systemFont(ofSize: 100.0)
        label.layer.opacity = 1.0
        addSubview(label)
    }
    
    @objc func tapped(_ event: UIEvent) {
        let location = tapGesture.location(in: self)
        
        // Forward to reference view.
        referenceViewModel.position = location
        
        let timing = UISpringTimingParameters(mass: 1, stiffness: 39.47841760435743, damping: 12.566370614359172, initialVelocity: .zero)
        animator = UIViewPropertyAnimator(duration: 0.0, timingParameters: timing)
        animator.addAnimations { [weak self] in
            self?.label.center = location
        }
        
        animator.startAnimation()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
0 Answers
Related