Position of UIImageView during an animation - ios

Today I make a shoot em up game without SpriteKit
And I have a problem for check UIImageView position during an animation.
Code I have tried:
#IBAction func MoveTDButton(_ sender: UIButton) {
animationForPlane = UIViewPropertyAnimator(duration: 0.5, curve: .easeIn, animations: {
if sender.tag == 1 {
self.ship.center.x = sender.frame.origin.x + sender.frame.size.width - (self.ship.frame.size.width / 2)
self.ship.image = #imageLiteral(resourceName: "shipright")
print(self.ship.center.x)
}else{
self.ship.center.x = sender.frame.origin.x + (self.ship.frame.size.width / 2)
self.ship.image = #imageLiteral(resourceName: "shipleft")
print(self.ship.center.x)
}
})
animationForPlane.startAnimation()
}
private func autoshoot(_ imgShip :UIImageView, _ imgBullet :UIImageView){
var t: TimeInterval!
t = 0.2
//var hauteur = self.view.frame.maxY
if let duration = t {
UIView.animate(withDuration: duration, animations: {
imgBullet.center.y = 10
imgBullet.center.x = self.ship.center.x
}, completion: { (true) in
imgBullet.center.x = self.ship.center.x
imgBullet.center.y = imgShip.center.y
//checkPosEnemy(img, hauteur: hauteur)
self.autoshoot(imgShip, imgBullet)
})
}
}
The function MoveTDButton it's for move my ship by button (left and right) with animation for the slide.
And, auto shoot function for shoot ^^
Problem :
When I click on a button for move my ship, position is set at the end of animation and not during.
So my bullet is not shoot at the ship's position but at the end of animation.

It will be better if you Use SpriteKit or similar frameworks.
However to answer to your question you can try :
imgBullet.center.x = self.ship.layer.presentation()!.position
instead of
imgBullet.center.x = self.ship.center.x

Related

Interactive UIView flip transition

I have the front and the back of a card. I animate the transition between the two like this:
private func flipToBack() {
UIView.transition(from: frontContainer, to: backContainer, duration: 0.5, options: [.transitionFlipFromRight, .showHideTransitionViews], completion: nil)
}
private func flipToFront() {
UIView.transition(from: backContainer, to: frontContainer, duration: 0.5, options: [.transitionFlipFromLeft, .showHideTransitionViews], completion: nil)
}
This works perfectly. However, I want to make this animation interactive, so that if the user pans horizontally across the card, the flip animation will advance proportionally. Usually, I would do this kind of interactive animation with a UIViewPropertyAnimator, but I do not know what property I would animate in the animator without building up the flip animation from scratch.
Is it possible to use UIViewPropertyAnimator, or is there some other alternative to make the flip interactive?
I ended up writing it myself. The code is pretty long, so here's a link to the full program on GitHub. Here are the key parts:
Everything is encapsulated in an InteractiveFlipAnimator object that takes a front view (v1) and a back view (v2). Each view also gets a black cover that functions as a shadow to add that darkening effect when the view turns in perspective.
Here is the panning function:
/// Add a `UIPanGestureRecognizer` to the main view that contains the card and pass it onto this function.
#objc func pan(_ gesture: UIPanGestureRecognizer) {
guard let view = gesture.view else { return }
if isAnimating { return }
let translation = gesture.translation(in: view)
let x = translation.x
let angle = startAngle + CGFloat.pi * x / view.frame.width
// If the angle is outside [-pi, 0], then do not rotate the view and count it as touchesEnded. This works because the full width is the screen width.
if angle < -CGFloat.pi || angle > 0 {
if gesture.state != .began && gesture.state != .changed {
finishedPanning(angle: angle, velocity: gesture.velocity(in: view))
}
return
}
var transform = CATransform3DIdentity
// Perspective transform
transform.m34 = 1 / -500
// y rotation transform
transform = CATransform3DRotate(transform, angle, 0, 1, 0)
self.v1.layer.transform = transform
self.v2.layer.transform = transform
// Set the shadow
if startAngle == 0 {
self.v1Cover.alpha = 1 - abs(x / view.frame.width)
self.v2Cover.alpha = abs(x / view.frame.width)
} else {
self.v1Cover.alpha = abs(x / view.frame.width)
self.v2Cover.alpha = 1 - abs(x / view.frame.width)
}
// Set which view is on top. This flip happens when it looks like the two views make a vertical line.
if abs(angle) < CGFloat.pi / 2 {
// Flipping point
v1.layer.zPosition = 0
v2.layer.zPosition = 1
} else {
v1.layer.zPosition = 1
v2.layer.zPosition = 0
}
// Save state
if gesture.state != .began && gesture.state != .changed {
finishedPanning(angle: angle, velocity: gesture.velocity(in: view))
}
}
The code to finish panning is very similar, but it is also much longer. To see it all come together, visit the GitHub link above.

if statement to change textLabel SKColor not executing

I am trying to run an if statement to change the color of a text label in SpriteKit where the number of lives has reached a certain number. But the color is not changing from white to red.
In my Game the Lives decrease by 1 every time an Asteroid passes the Spaceship.
Right now the text label code looks like this:
livesLabel.text = "Lives: 3"
livesLabel.fontSize = 70
if livesNumber == 1 {
livesLabel.fontColor = SKColor.red
} else {
livesLabel.fontColor = SKColor.white
}
livesLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.right
if UIDevice().userInterfaceIdiom == .phone {
switch UIScreen.main.nativeBounds.height {
case 2436: // Checks if device is iPhone X
livesLabel.position = CGPoint(x: self.size.width * 0.79, y: self.size.height * 0.92)
default: // All other iOS devices
livesLabel.position = CGPoint(x: self.size.width * 0.85, y: self.size.height * 0.95)
}
}
livesLabel.zPosition = 100
self.addChild(livesLabel)
I have a function where a livesNumber if statement works fine which looks like this:
func loseALife() {
livesNumber -= 1
livesLabel.text = "Lives: \(livesNumber)"
let scaleUp = SKAction.scale(to: 1.5, duration: 0.2)
let scaleDown = SKAction.scale(to: 1, duration: 0.2)
let scaleSequence = SKAction.sequence([scaleUp, scaleDown])
livesLabel.run(scaleSequence)
if livesNumber == 0{
runGameOver()
}
}
If anyone can shed a light on this that would be great. I thought that it might have something to do with String or Int, but since this works on other functions and also I tried some String to Int conversions and it changed nothing I am no longer sure what the issue is.
As can be seen from the discussion above, the problem was that the "color label" code was run once in the initialization of the view. Therefore, when the livesNumber value was later updated, the "color label" code wasn't executed.
This can be solved in several ways. One would be to add it to the looeALife function:
func loseALife() {
livesNumber -= 1
livesLabel.text = "Lives: \(livesNumber)"
if livesNumber == 1 {
livesLabel.fontColor = SKColor.red
} else {
livesLabel.fontColor = SKColor.white
}
let scaleUp = SKAction.scale(to: 1.5, duration: 0.2)
let scaleDown = SKAction.scale(to: 1, duration: 0.2)
let scaleSequence = SKAction.sequence([scaleUp, scaleDown])
livesLabel.run(scaleSequence)
if livesNumber == 0{
runGameOver()
}
}
(maybe extract it to a separate function)
You could also add a didSet to your livesNumber and then update the value there:
var livesNumber: Int = 3 {
didSet {
livesLabel.color = livesNumber == 1 ? .red : .white
}
}
Hope that helps.

Stop UIDynamicAnimator Whit view Limits

I have been trying to create a custom UIClass that implements an horizontal scrolling for its inner content by using UIPanGestureRecognizer.
My first success approach was:
#IBAction func dragAction(_ sender: UIPanGestureRecognizer) {
let velocity = sender.velocity(in: sender.view)
if velocity.x > 0{
//print("\(logClassName) Dragging Right ...")
if innerView!.frame.origin.x < CGFloat(0){
offSetTotal += 1
innerView?.transform = CGAffineTransform(translationX: CGFloat(offSetTotal), y: 0)
}
}
else{
//print("\(logClassName) Dragging Left ...")
if totalWidth - innerView!.bounds.maxX < innerView!.frame.origin.x{
offSetTotal -= 1
innerView?.transform = CGAffineTransform(translationX: CGFloat(offSetTotal), y: 0)
}
}
}
That way, althought is a little bit clunky, I am able to scroll from left to right (and reverse) and the innerview is always covering in the holderView.
Then, and because i wanted to get the scrolling effect that you would get with a UIScrolling view i tried the following approach
#objc func handleTap(_ pan: UIPanGestureRecognizer){
let translation = pan.translation(in: innerView)
let recogView = pan.view
let curX = recogView?.frame.origin.x
if pan.state == UIGestureRecognizerState.began {
self.animator.removeAllBehaviors()
recogView?.frame.origin.x = curX! + translation.x
pan.setTranslation(CGPoint.init(x: 0, y: 0), in: recogView)
}else if pan.state == UIGestureRecognizerState.changed {
recogView?.frame.origin.x = curX! + translation.x
pan.setTranslation(CGPoint.init(x: 0, y: 0), in: recogView)
}else if pan.state == UIGestureRecognizerState.ended {
let itemBehavior = UIDynamicItemBehavior(items: [innerView!]);
var velocity = pan.velocity(in: self)
print(velocity.y)
velocity.y = 0
itemBehavior.addLinearVelocity(velocity, for: innerView!);
itemBehavior.friction = 1
itemBehavior.resistance = 1;
//itemBehavior.elasticity = 0.8;
self.collision = UICollisionBehavior(items: [innerView!]);
self.collision!.collisionMode = .boundaries
self.animator.addBehavior(collision!)
self.animator.addBehavior(itemBehavior);
}
Now the problem i face is that it moves horizontally but it goes away and gets lose.
Is that the right approach? Is there a delegate?
The question was posted due to a lack of knowledge of UIScrollViews. I Knew how to do with Storyboard and only scroll vertically.
I ended up creating a programatically UIScrollView and setting up its content size with the inner view.
scrollView = UIScrollView(frame: bounds)
scrollView?.backgroundColor = UIColor.yellow
scrollView?.contentSize = innerView!.bounds.size
scrollView?.addSubview(innerView!)
addSubview(scrollView!)
But first I have defined the innerView.

How to animate to rotate counter-clockwise

I have a segmented control and I'd like to animate a UIView to rotate clockwise when the right side of the control is tapped, and counter-clockwise when the left side is tapped. My code to rotate is:
func rotateRight() {
UIView.animate(withDuration: 0.5, animations: {
self.profileImageView.transform = CGAffineTransform(rotationAngle: (180.0 * CGFloat(M_PI)) / 180.0)
self.profileImageView.transform = CGAffineTransform(rotationAngle: (0.0 * CGFloat(M_PI)) / 360.0)
})
And then I'm using this code to handle the changes depending on which segment is tapped (Edited for update):
if loginRegisterSegmentedControl.selectedSegmentIndex == 0 {
self.loginRegisterButton.backgroundColor = UIColor.blue
loginRegisterSegmentedControl.tintColor = UIColor.blue
rotateLeft()
// Roll to the left
} else {
self.loginRegisterButton.backgroundColor = UIColor.red
loginRegisterSegmentedControl.tintColor = UIColor.red
rotateRight()
// Roll to the right
}
So basically in the if conditional I want to call a rotateLeft() function - how can I do this?
EDIT: Here's my full code for the animation, including Pierce's suggestion:
// Rotation
var viewAngle: CGFloat = 0 // Right-side up to start
let π = CGFloat.pi // Swift allows special characters hold alt+p to use this, it allows for cleaner code
func rotate(by angle: CGFloat) {
self.viewAngle += angle
UIView.animate(withDuration: 0.5, animations: {
self.profileImageView.transform = CGAffineTransform(rotationAngle: self.viewAngle)
self.view.layoutIfNeeded()
})
}
lazy var profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "TTTdude")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(rotate)))
imageView.isUserInteractionEnabled = true
return imageView
}()
lazy var loginRegisterSegmentedControl: UISegmentedControl = {
let sc = UISegmentedControl(items: ["iOS", "Android"])
sc.translatesAutoresizingMaskIntoConstraints = false
sc.tintColor = UIColor.black
sc.selectedSegmentIndex = 0
sc.addTarget(self, action: #selector(handleLoginRegisterChange), for: .valueChanged)
return sc
}()
func handleLoginRegisterChange() {
let title = loginRegisterSegmentedControl.titleForSegment(at: loginRegisterSegmentedControl.selectedSegmentIndex)
loginRegisterButton.setTitle(title, for: .normal)
if loginRegisterSegmentedControl.selectedSegmentIndex == 0 {
self.loginRegisterButton.backgroundColor = UIColor.blue
loginRegisterSegmentedControl.tintColor = UIColor.blue
rotate(by: -2*π)
// Roll to the left
} else {
self.loginRegisterButton.backgroundColor = UIColor.red
loginRegisterSegmentedControl.tintColor = UIColor.red
rotate(by: 2*π)
// Roll to the right
}
Just rotate it by whatever angle you did for clockwise, but multiply it by negative one
func rotateLeft() {
UIView.animate(withDuration: 0.5, animations: {
self.profileImageView.transform = CGAffineTransform(rotationAngle: ((180.0 * CGFloat(M_PI)) / 180.0) * -1)
self.profileImageView.transform = CGAffineTransform(rotationAngle: ((0.0 * CGFloat(M_PI)) / 360.0) * -1)
self.view.layoutIfNeeded()
})
}
I should also mention that when you say (180.0 * CGFloat(π)) / 180 - You're just saying π, because 180/180 is one.
It looks to me like you're trying to do two different rotations at once? I'm a little confused by that. One thing I've found that really helps is to track the view's axis orientation if you're doing multiple rotations both clockwise and counter-clockwise. Do something like create a property called viewAngle
var viewAngle: CGFloat = 0 // Right-side up to start
let π = CGFloat.pi // Swift allows special characters hold alt+p to use this, it allows for cleaner code
func rotate(by angle: CGFloat) {
for i in 0 ..< 4 {
UIView.animate(withDuration: 0.125, delay: 0.125 * Double(i), options: .curveLinear, animations: {
self.viewAngle += angle/4
self.rotateView.transform = CGAffineTransform(rotationAngle: self.viewAngle)
self.view.layoutIfNeeded()
}, completion: nil)
}
}
If you want to rotate right pass in a positive angle (i.e. π, π/2, π/4, 2*π), or if left pass in a negative angle.
Rotate right by 180 degrees:
rotate(by: π)
Rotate left by 180 degrees:
rotate(by: -π)
EDIT: As you were mentioning, which I didn't see until I tried for myself, when you plug in π or 2π whether it's negative or positive, it just rotates clockwise. To get around this I had to make it rotate by -π/4 increments. So to do a full-circle rotate counter clockwise I did a loop of -π/4 animations if that makes sense. It works, but I feel like there should be an easier way to do this. I would appreciate anyone else chiming in.
(swift 4)
it will rotate 180 degree counter clock wise
self.viewRoatate.transform = CGAffineTransform(rotationAngle: CGFloat.pi / -2)
self.viewRoatate.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
self.view.layoutIfNeeded()
func rotate(button: UIButton, time: Double, overlap: Double, clockWise: Bool) {
var angles = [CGFloat(Double.pi), CGFloat(Double.pi) * 2]
if !clockWise {
angles = [CGFloat(Double(270) * .pi/180),
CGFloat(Double(180) * .pi/180),
CGFloat(Double(90) * .pi/180),
CGFloat(Double(0) * .pi/180)]
}
for i in 0..<angles.count {
UIView.animate(withDuration: time, delay: time - overlap * Double(i), animations: {
button.transform = CGAffineTransform(rotationAngle: angles[i])
}, completion: nil)
}
By using CABasicAnimation:
left-rotation(counter-clockwise: -360) & right-rotation(clockwise: 360)
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.toValue = direction == .left ? -2 * (CGFloat.pi) : 2 * CGFloat.pi
animation.duration = 1
button.layer.add(animation, forKey: nil)

Core Animation + UIDynamics + arc4random()

I have a single UFO that is an UIImage and I simply want it to float around and come in and off at random locations like the asteroids in the game Asteroids. I put some rotation and grow in there temporarily. It would be nice if all of the animations were done randomly. Even if it was only random motion I'd be super happy. How do I implement arc4random()? After I accomplish that simple task I'd like to learn how to apply arc4random() to various behaviours in UIDynamics. Thanks in advance.
import UIKit
class ViewController: UIViewController {
#IBOutlet var ufo: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
UIView.animateWithDuration(5.5, delay: 0, options: .Repeat, animations: {
let grow = CGAffineTransformMakeScale(2, 2)
let angle = CGFloat( -30)
let rotate = CGAffineTransformMakeRotation(angle)
self.ufo.transform = CGAffineTransformConcat(grow, rotate)
}, completion: nil)
}
}
arc4random_uniform(n) will return a number from 0 to n. If you want this to return an Int then you can cast it as below:
var randomNumber = Int(arc4random_uniform(7))
Use this function to generate random numbers and then use those numbers in your animation.
For example, if you want your animation to have a random duration:
var duration = Double(arc4random_uniform(5)+1)
var x = CGFloat(arc4random_uniform(320))
var y = CGFloat(arc4random_uniform(960))
UIView.animateWithDuration(duration, delay: 0, options: .Repeat, animations: {
let grow = CGAffineTransformMakeScale(2, 2)
let angle = CGFloat( -30)
let rotate = CGAffineTransformMakeRotation(angle)
self.ufo.transform = CGAffineTransformConcat(grow, rotate)
self.ufo.frame = CGRectMake(x,y,self.ufo.frame.size.width, self.ufo.frame.size.height)
}, completion: nil)
The reason I have the arc4random_uniform(5)+1 is because the function is from 0 to n as I mentioned above, so 0 to 5 in this case but we do not want an animation with 0 duration so I added 1 to it to make the number 1 to 6.

Resources