CGAffineTransformRotate issue with image - ios

I'm trying to create a very simple clock. When I apply a CGAffineTransformRotate it looks great, however on the 30th - 31st second the hand goes crazy.
An example of it going wrong.
Here is the code I'm using to animate it:
lengthInSeconds += 1
let angle = CGFloat(6.degreesToRadians * Double(lengthInSeconds))
var transform = CGAffineTransformMakeTranslation(0.0, (clockFace.frame.width/4))
secondsHand.layer.anchorPoint = CGPointMake(0.5, 1.0)
transform = CGAffineTransformRotate(transform, angle)
let spring = CASpringAnimation(keyPath: "transform.rotation")
spring.damping = 2.0//3.0
spring.duration = 0.4
spring.mass = 0.1
spring.initialVelocity = 0.0
secondsHand.layer.addAnimation(spring, forKey: "rotation")
secondsHand.transform = transform
Anyone have any idea as to why it would rotate awkwardly like this? Just to add, it happens on all passes, so at 30 seconds, 1 minute 30, 2 minutes 30 etc.

Related

Shoot Arrow in the direction that the Bow is facing

I am creating a iOS Game where a player has a bow and arrow that spins in a 360 degree circle, and the player must shoot the bow at the right time to hit the target. Right now I am having trouble getting the arrow to shoot in the direction the bow is facing, as well as getting the arrow to shoot at the right angle towards that direction.
let bullet = SKSpriteNode(fileNamed: "Bullet")
bullet?.size = CGSize(width: 100, height: 100)
bullet.zPosition = -5
bullet.position = CGPointMake(player.position.x, player.position.y)
bullet.zRotation = player.zRotation
let action = SKAction.moveToY(self.size.height + 30, duration: 0.8)
let actionDone = SKAction.removeFromParent()
bullet.runAction(SKAction.sequence([action, actionDone]))
bullet.physicsBody = SKPhysicsBody(rectangleOfSize: bullet.size)
bullet.physicsBody?.affectedByGravity = false
bullet.physicsBody?.dynamic = false
self.addChild(bullet)
The player refers the the bow for reference.
Instead of moving the bullet to self.size.height + 30 in the Y direction and 0 pixels in the X direction, you can rotate that movement direction by the zRotation using trigonometry.
let amount = self.size.height + 30
let action = SKAction.moveTo(CGPointMake(bullet.position.x + amount * sin(bullet.zRotation), bullet.position.y + amount * cos(bullet.zRotation)), duration: 0.8)
You can get the behavior you are looking for by calculating a force vector from the bullet's zRotation and then use it to apply a force to the bullet's physicsBody.
To do this we will use trigonometry.
//adjust rotation by pi/2 radians to match spriteKits rotation system
let adjustedRotation = bullet.zRotation + .pi/2
//intensity scalar
let intensity:CGFloat = 4000 //adjust this value
//find x and y components using adjustedRotation and scale by intensity
let vx = intensity * cos(adjustedRotation)
let vy = intensity * sin(adjustedRotation)
//make vector using vx and vy components
let forceVector = CGVector(dx:vx, dy: vy)
//apply force to physicsBody
bullet.physicsBody?.applyForce(forceVector)

Make a button rotate counterclockwise using CGAffineTransform swift 3 syntax?

I'm trying to make a button rotate counterclockwise but for some strange reason it's rotating clockwise. I know the previous way to do it is by M_PI but it's been deprecated for swift 3 and replaced with CGFloat.pi. I've tried:
self.loginButton.transform = CGAffineTransform(rotationAngle: -CGFloat.pi)
but it still moves clockwise. Any idea what the syntax is to move counterclockwise?
The animation will always take the shortest way. Therefore CGFloat.pi and -CGFloat.pi animates in same position.
As we need anti-clockwise rotation we forcefully made it a shortest way by using this -(CGFloat.pi * 0.999).
UIView.animate(withDuration: 1.0, animations:{
self.loginButton.transform = CGAffineTransform(rotationAngle:-(CGFloat.pi * 0.999))
})
There is a better solution than this use CABasicAnimation for anticlockwise rotation.
let anticlockAnimation = CABasicAnimation(keyPath: "transform.rotation")
anticlockAnimation.fromValue = CGFloat.pi
anticlockAnimation.toValue = 0
anticlockAnimation.isAdditive = true
anticlockAnimation.duration = 1.0
self.loginButton.layer.add(anticlockAnimation, forKey: "rotate")
self.loginButton.transform = CGAffineTransform(rotationAngle: -CGFloat.pi)

Mysterious factor of 150 on SpriteKit's Physics. Gravity and Forces

I wanted an object to float on the screen, resisting gravity, not moving at all.
This is the gravity setting of the view.
self.physicsWorld.gravity = CGVector(dx: 0, dy: 5.0)
it's set to 5m/s^2 upwards. So object gets accelerated by 5m upwards per second.
Mass of the object is set to 1.0kg
self.physicsBody?.mass = 1.0
I applied a force to the object so it can resist the gravity. So I did the following.
func update(delta: TimeInterval) {
...
let force = CGVector(dx: 0.0, dy: -5.0)
self.physicsBody?.applyForce(force)
}
I applied -5N because I thought the gravitational force applied to the object is 1kg * 5m/s^2 = 5N. Applying -5N will make the object gets accelerated by -5m/s^2, floating on the screen as a result with the gravity.
But it did not work. Instead I had to do this.
let force = CGVector(dx: 0.0, dy: -5.0 * 150.0)
-5 multiplied by 150 is -750. So, where does this 150 come from? Why do I have to apply -750N instead of -5N to make the object resist gravity?
I also tested out different masses and forces on different gravity settings.
self.physicsBody?.mass = 2.0
let force = CGVector(dx: 0.0, dy: -5.0 * 150.0 * 2)
self.physicsWorld.gravity = CGVector(dx: 0, dy: 15.0)
self.physicsBody?.mass = 2.0
let force = CGVector(dx: 0.0, dy: -15.0 * 150.0 * 2)
and they all worked find. F=ma.
Question is the mysterious factor of 150. Where the hell does it come form?
OK, so it was all about wrong documentation of Apple.
Here's the truth of 150.
this seems to be little bit stupid, but applyForce is measured in ((points * kilo) / sec ^ 2), but the gravity acceleration is in Newtons ((kilo * meter)/ sec ^ 2) (despite the fact it's described as meters per second in documentation. Meters per second! Acceleration!). Multiply it by mass and get the force.
https://stackoverflow.com/a/31868380/5752908
Come on, Apple... It's been out there for 4 years.

Connect two strokeEnd animations

I am trying to make a custom activity indicator similar to the Android material design circular indeterminate activity indicator. Basically I want to draw the circle two times and erase it, but erasing and drawing does not happen at the same time or speed. This is what I have so far:
let progressLayer = CAShapeLayer()
progressLayer.strokeColor = UIColor.red().cgColor
progressLayer.fillColor = nil
progressLayer.lineWidth = 2
let drawAnimation = CABasicAnimation(keyPath: "strokeEnd")
drawAnimation.duration = duration / 2
drawAnimation.fromValue = 0
drawAnimation.toValue = 1
drawAnimation.isRemovedOnCompletion = false
drawAnimation.fillMode = kCAFillModeForwards
let eraseAnimation = CABasicAnimation(keyPath: "strokeStart")
eraseAnimation.duration = duration / 2
eraseAnimation.beginTime = 0.2
eraseAnimation.fromValue = 0
eraseAnimation.toValue = 0.4
eraseAnimation.isRemovedOnCompletion = false
eraseAnimation.fillMode = kCAFillModeForwards
let endDrawAnimation = CABasicAnimation(keyPath: "strokeEnd")
endDrawAnimation.beginTime = duration / 2
endDrawAnimation.duration = duration / 2
endDrawAnimation.fromValue = 0
endDrawAnimation.toValue = 1
endDrawAnimation.isRemovedOnCompletion = false
endDrawAnimation.fillMode = kCAFillModeForwards
let endEraseAnimation = CABasicAnimation(keyPath: "strokeStart")
endEraseAnimation.beginTime = duration / 2
endEraseAnimation.duration = duration / 4
endEraseAnimation.fromValue = 0.4
endEraseAnimation.toValue = 1
endEraseAnimation.isRemovedOnCompletion = false
endEraseAnimation.fillMode = kCAFillModeForwards
let endEraseAnimation2 = CABasicAnimation(keyPath: "strokeStart")
endEraseAnimation2.beginTime = duration * 3 / 4
endEraseAnimation2.duration = duration / 4
endEraseAnimation2.fromValue = 0
endEraseAnimation2.toValue = 1
endEraseAnimation2.isRemovedOnCompletion = false
endEraseAnimation2.fillMode = kCAFillModeForwards
let animations = CAAnimationGroup()
animations.duration = duration
animations.animations = [drawAnimation, eraseAnimation, endDrawAnimation, endEraseAnimation, endEraseAnimation2]
animations.isRemovedOnCompletion = false
animations.fillMode = kCAFillModeForwards
progressLayer.add(animations, forKey: "stroke")
The code does everything as expected, except for one issue. When the first strokeEnd animation is done and the second one starts there is sort of a flash meaning the part of the circle that was drawn till that point disappears and then drawing starts again from 0. Does anyone have any ideas how to fix this?
When the first strokeEnd animation is done and the second one starts
there is sort of a flash meaning the part of the circle that was drawn
till that point disappears and then drawing starts again from 0.
If you compare the setup of both drawAnimation and endDrawAnimation you will see that they are identical except for the beginTime property. That is what causes your flickering. They both start at 0 and they both end with 1. Since you have not specified the desired outcome I can just assume that you want endDrawAnimation to have a fromValue of 1.
let drawAnimation = CABasicAnimation(keyPath: "strokeEnd")
drawAnimation.duration = duration / 2
drawAnimation.fromValue = 0
drawAnimation.toValue = 1
drawAnimation.isRemovedOnCompletion = false
drawAnimation.fillMode = kCAFillModeForwards
let endDrawAnimation = CABasicAnimation(keyPath: "strokeEnd")
endDrawAnimation.beginTime = duration / 2 // Starts after the first animation and starts with 0 again.
endDrawAnimation.duration = duration / 2
endDrawAnimation.fromValue = 0
endDrawAnimation.toValue = 1
endDrawAnimation.isRemovedOnCompletion = false
endDrawAnimation.fillMode = kCAFillModeForwards
Suggestion for improvement
As far as I can see you want to create key frame animation meaning that you have predefined offsets at which the animation behaviour (speed, value, etc.) changes. You might want to consider using CAKeyframeAnimation instead.
I am unsure if you are aware of this but at the moment you have two animations running concurrently on strokeStart:
eraseAnimation that starts at 0.2 with a duration of 0.5 * duration
endEraseAnimation that starts at 0.5 * duration.
That means that the end of eraseAnimation (0.2 + 0.5 * duration) is always greater than the start of endEraseAnimation (0.5 * duration)

How can I rotate an UIImageView by 20 degrees?

What do I have to do, if I need to rotate a UIImageView? I have a UIImage which I want to rotate by 20 degrees.
The Apple docs talk about a transformation matrix, but that sounds difficult. Are there any helpful methods or functions to achieve that?
If you want to turn right, the value must be greater than 0 if you want to rotate to the left indicates the value with the sign "-". For example -20.
CGFloat degrees = 20.0f; //the value in degrees
CGFloat radians = degrees * M_PI/180;
imageView.transform = CGAffineTransformMakeRotation(radians);
Swift 4:
let degrees: CGFloat = 20.0 //the value in degrees
let radians: CGFloat = degrees * (.pi / 180)
imageView.transform = CGAffineTransform(rotationAngle: radians)
A transformation matrix is not incredibly difficult. It's quite simple, if you use the supplied functions:
imgView.transform = CGAffineTransformMakeRotation(.34906585);
(.34906585 is 20 degrees in radians)
Swift 5:
imgView.transform = CGAffineTransform(rotationAngle: .34906585)
Swift version:
let degrees:CGFloat = 20
myImageView.transform = CGAffineTransformMakeRotation(degrees * CGFloat(M_PI/180) )
Swift 4.0
imageView.transform = CGAffineTransform(rotationAngle: CGFloat(20.0 * Double.pi / 180))
Here's an extension for Swift 3.
extension UIImageView {
func rotate(degrees:CGFloat){
self.transform = CGAffineTransform(rotationAngle: degrees * CGFloat(M_PI/180))
}
}
Usage:
myImageView.rotate(degrees: 20)
_YourImageView.transform = CGAffineTransformMakeRotation(1.57);
where 1.57 is the radian value for 90 degree
This is an easier formatting example (for 20 degrees):
CGAffineTransform(rotationAngle: ((20.0 * CGFloat(M_PI)) / 180.0))
As far as I know, using the matrix in UIAffineTransform is the only way to achieve a rotation without the help of a third-party framework.

Resources