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
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")
}
}
