I've tried to animate custom UIViewRepresentable with SwitfUI animations, but it doesn't work. It just sets value without interpolation. What should i do to use interpolation values in UIKit views?
My example shows two "progress bars" red one is UIKit view, blue one is SwiftUI version. Sliders controls value directly, randomize button changes value to random with 5s animation.
When I press button SwiftUI progress bar animates exactly as it should, but UIKit's one just jumps to final position.
Set block of animatableData inside Animatable extension not called.
How can I use SwiftUI animation value interpolations for UIKit?
import SwiftUI
import UIKit
class UIAnimationView: UIView {
var progress: CGFloat = 0.5 {
didSet {
if self.progressConstraint != nil, self.innerView != nil {
self.removeConstraint(self.progressConstraint!)
}
let progressConstraint = NSLayoutConstraint(
item: innerView!,
attribute: .trailing,
relatedBy: .equal,
toItem: self,
attribute: .trailing,
multiplier: min(1.0, max(0.0001, progress)),
constant: 0
)
self.addConstraint(progressConstraint)
self.progressConstraint = progressConstraint
self.layoutIfNeeded()
}
}
var innerView: UIView?
private var progressConstraint: NSLayoutConstraint?
public override init(frame: CGRect) {
super.init(frame: frame)
self.performInit()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.performInit()
}
private func performInit() {
let innerView = UIView()
innerView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(innerView)
self.leadingAnchor.constraint(equalTo: innerView.leadingAnchor).isActive = true
self.topAnchor.constraint(equalTo: innerView.topAnchor).isActive = true
self.bottomAnchor.constraint(equalTo: innerView.bottomAnchor).isActive = true
let progressConstraint = NSLayoutConstraint(
item: innerView,
attribute: .trailing,
relatedBy: .equal,
toItem: self,
attribute: .trailing,
multiplier: progress,
constant: 0
)
self.progressConstraint = progressConstraint
self.addConstraint(progressConstraint)
self.innerView = innerView
self.innerView!.backgroundColor = UIColor.red
self.backgroundColor = UIColor.black
}
}
struct AnimationTest: UIViewRepresentable {
var progress: CGFloat
typealias UIViewType = UIAnimationView
func updateUIView(_ uiView: UIAnimationView, context: Context) {
print("progress: \(progress) \(context.transaction.isContinuous)")
uiView.progress = progress
}
func makeUIView(context: Context) -> UIAnimationView {
let view = UIAnimationView()
view.progress = progress
return view
}
}
extension AnimationTest: Animatable {
var animatableData: CGFloat {
get {
return progress
}
set {
print("Animation \(newValue)")
progress = newValue
}
}
}
struct AnimationDebug: View {
@State var progress: CGFloat = 0.75
var body: some View {
VStack {
AnimationTest(progress: progress)
Spacer()
VStack {
Slider(value: $progress, in: 0...1) {
Text("Progress")
}
}
GeometryReader { gr in
Color.blue
.frame(
width: gr.size.width * progress,
height: 48)
}
.frame(height: 48)
Button("Randomize") {
withAnimation(Animation.easeInOut(duration: 5)) {
progress = CGFloat.random(in: 0...1)
}
}
}
}
}
struct AnimationTest_Previews: PreviewProvider {
static var previews: some View {
AnimationDebug()
}
}