Rotate an image with animation and then read it

I implement a wheel of fortune with CAKeyframeAnimation and try to read the result after the animation stops. But here I do not get deterministic results. Is it impossible to read the rotation after the animation stops?

Here is my animation code:

let anim = CAKeyframeAnimation(keyPath: "transform.rotation.z") anim.duration = max(strength / 2, 1.0) anim.isCumulative = true anim.values = [NSNumber(value: Float(p)), Float(circleRotationOffset)] anim.keyTimes = [NSNumber(value: Float(0)),NSNumber(value: Float(1.0))] anim.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)] anim.isRemovedOnCompletion = false anim.fillMode = kCAFillModeForwards anim.delegate = self wheelImage.layer.removeAllAnimations() wheelImage.layer.add(anim, forKey: "rotate") 

And this is how I read the rotation:

 func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { readImageOrientation() } func readImageOrientation(){ let radians:Double = atan2( Double(wheelImage.transform.b), Double(wheelImage.transform.a)) let degrees:CGFloat = CGFloat(radians) * (CGFloat(180) / CGFloat(M_PI)) sectionForDegrees(degree: degrees) } 

For completeness, here is my complete class.

 class WOFView: UIView, CAAnimationDelegate { @IBOutlet weak var wheelImage: UIImageView! private var history = [Dictionary<String, Any>]() private var rotation: CGFloat = 0 private var startAngle: CGFloat = 0 private var circleRotationOffset: CGFloat = 0 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) if let touchPoint = touches.first?.location(in: self){ if startAngle == 0{ startAngle = atan2(self.frame.width - touchPoint.y, self.frame.height - touchPoint.x) } rotation = startAngle if !touch(touches.first!, isInLeftHalfOf: wheelImage) { rotation = -rotation } history.removeAll() } } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesMoved(touches, with: event) guard let touchPoint = touches.first?.location(in: self) else { return } let dic = ["time" : NSNumber(value: CFAbsoluteTimeGetCurrent()), "point": NSValue(cgPoint: touchPoint), "rotation": NSNumber(value: Float(circleRotationOffset + rotation))] history.insert(dic, at: 0) if history.count == 3{ history.removeLast() } rotation = atan2(self.frame.width - touchPoint.y, self.frame.height - touchPoint.x) - startAngle if !touch(touches.first!, isInLeftHalfOf: wheelImage) { rotation = -rotation } wheelImage.transform = CGAffineTransform(rotationAngle: circleRotationOffset + rotation) print("offset: \(circleRotationOffset)") readImageOrientation() } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesEnded(touches, with: event) guard let touchPoint = touches.first?.location(in: self) else { return } guard let lastObject = history.last else{ return } guard let pointValue = lastObject["point"] as? CGPoint else{ return } guard let timeValue = lastObject["time"] as? NSNumber else { return } guard let rotationValue = lastObject["rotation"] as? NSNumber else { return } let timeDif = CFAbsoluteTimeGetCurrent() - (timeValue.doubleValue) circleRotationOffset = circleRotationOffset + rotation let lastRotation = rotationValue.floatValue let dist = sqrt(((pointValue.x - touchPoint.x) * (pointValue.x - touchPoint.x)) + ((pointValue.y - touchPoint.y) * (pointValue.y - touchPoint.y))) let strength = max(Double(min(1.0, dist / 80.0)) * (timeDif / 0.25) * M_PI * 2, 0.3) * 30 let p = circleRotationOffset let dif = circleRotationOffset - CGFloat(lastRotation) var inc = dif > 0 if dif > 3 || dif < -3{ inc = !inc } if (inc){ circleRotationOffset += CGFloat(strength) }else{ circleRotationOffset -= CGFloat(strength) } let anim = CAKeyframeAnimation(keyPath: "transform.rotation.z") anim.duration = max(strength / 2, 1.0) anim.isCumulative = true anim.values = [NSNumber(value: Float(p)), Float(circleRotationOffset)] anim.keyTimes = [NSNumber(value: Float(0)),NSNumber(value: Float(1.0))] anim.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)] anim.isRemovedOnCompletion = false anim.fillMode = kCAFillModeForwards anim.delegate = self wheelImage.layer.removeAllAnimations() wheelImage.layer.add(anim, forKey: "rotate") } func touch(_ touch:UITouch, isInLeftHalfOf view: UIView) -> Bool { let positionInView = touch.location(in: view) return positionInView.x < view.frame.midX } func touch(_ touch:UITouch, isInUpperHalfOf view: UIView) -> Bool { let positionInView = touch.location(in: view) return positionInView.y < view.frame.midY } func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { readImageOrientation() } func readImageOrientation(){ let radians:Double = acos(Double(wheelImage.transform.a)) let degrees:CGFloat = CGFloat(radians) * (CGFloat(180) / CGFloat(M_PI)) sectionForDegrees(degree: degrees) } func sectionForDegrees(degree: CGFloat){ var result = "not defined" switch degree{ case 0 ... 90: result = "3 \(degree)" case 90.1...180: result = "2 \(degree)" case 181.1...270: result = "1 \(degree)" case 270.1...360: result = "4 \(degree)" default: result = "not defined: \(degree)" } print(result) } 

}

xib file screenshot

+5
source share
2 answers

According to this Objective-C answer, there are (at least) four ways to get the rotation:

 let view = UIImageView() view.transform = CGAffineTransform(rotationAngle: 0.02); let x = view.value(forKeyPath: "layer.transform.rotation.z") let a = acos(view.transform.a) let b = asin(view.transform.b) let c = atan2(view.transform.b, view.transform.a) print(a) print(b) print(c) print(x!) 

Print

 0.0200000000000011 0.02 0.02 0.02 
+2
source

Original answer (general thoughts about transformations)

I think you should use acos() instead of atan2() , based on what the three-dimensional rotation matrix looks like:

rotation matrix

In this Rz matrix, we see that transform.a and transform.b will be defined as cosine theta. Ref CGAffineTransform Documentation :

https://developer.apple.com/reference/coregraphics/cgaffinetransform

Without testing, I'm sure a simple acos(Double(wheelImage.transform.a)) will be enough to give you a theta rotation. EDIT: This is bad advice , replacing atan2 with acos means you need to check if your answer is a positive or negative cosine value, and is completely pointless. Another thing to keep in mind is that the transformation will also change if you apply any scaling to your wheel.

If I'm wrong, you can always use the value of strength to calculate how long you are going to rotate the wheel and find the angle from it. This will require you to learn how kCAMediaTimingFunctionEaseOut works, so I would suggest you change the animation to first calculate the angle (which you save as a parameter), then apply it directly to layer.transform instead of using duration as a deciding factor.


Updated Answer

After looking at your complete code, I found out that you misinterpreted this angle, here is a rewrite of your sectionForDegrees function, which will give the correct section:

 func sectionForDegrees(degree: CGFloat){ var result = "not defined" switch degree{ case 0.1 ... 90: result = "1 \(degree)" case 90.1...180: result = "4 \(degree)" case -90.1...0: result = "2 \(degree)" case -180...(-90): result = "3 \(degree)" default: result = "not defined: \(degree)" } print(result) } 
+1
source

Source: https://habr.com/ru/post/1262733/


All Articles