Reputation: 29
I took Paul Hudson's (Hacking with Swift) animation tutorial (His Project 15) and tried to extend it to see the effect of multiple animations layered on one another.
There are four distinct animations: scale, translate, rotate and color. Instead of a single button that cycles through all of them, as in his tutorial, I used four buttons to allow selection of each animation individually. I also modified his "undo" animation by reversing the original animation rather than using CGAffineTransform.identity, since the identity transform would undo all the animations to that point.
My problem is that when I click my c button, it does the appropriate scaling the first click but, rather than scale the penguin back to its original size on the second click, it rotates the view. Subsequent clicks of the scale button continue to rotate the view as if I were clicking the rotate button.
There are other anomalies, such as the first click of the move button moves the penguin appropriately but following that with a click of the rotate button, both rotates the penguin and moves it back to the original position. I'm not sure why it moves back but I accept that that might be my own ignorance of the animation system.
I've added print statements and put in breakpoints to debug the scale problem. Everything in the code seems to be working exactly as coded but the animations defy logic! Any help would be appreciated.
The complete program is relatively simple:
import UIKit
class ViewController: UIViewController {
var imageView: UIImageView!
var scaled = false
var moved = false
var rotated = false
var colored = false
@IBOutlet var scaleButton: UIButton!
@IBOutlet var moveButton: UIButton!
@IBOutlet var rotateButton: UIButton!
@IBOutlet var colorButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
imageView = UIImageView(image: UIImage(named: "penguin"))
imageView.center = CGPoint(x: 200, y: 332)
view.addSubview(imageView)
}
@IBAction func tapped(_ sender: UIButton) {
sender.isHidden = true
var theAnimation: ()->Void
switch sender {
case scaleButton:
theAnimation = scaleIt
case moveButton:
theAnimation = moveIt
case rotateButton:
theAnimation = rotateIt
case colorButton:
theAnimation = colorIt
default:
theAnimation = { self.imageView.transform = .identity }
}
print("scaled = \(scaled), moved = \(moved), rotated = \(rotated), colored = \(colored)")
UIView.animate(withDuration: 1, delay: 0, options: [], animations: theAnimation) { finished in
sender.isHidden = false
}
}
func scaleIt() {
print("scaleIt()")
let newScale: CGFloat = self.scaled ? -1.5 : 1.5
self.imageView.transform = CGAffineTransform(scaleX: newScale, y: newScale)
self.scaled.toggle()
}
func moveIt() {
print("moveIt()")
let newX: CGFloat = self.moved ? 0 : -50
let newY: CGFloat = self.moved ? 0 : -150
self.imageView.transform = CGAffineTransform(translationX: newX, y: newY)
self.moved.toggle()
}
func rotateIt() {
print("rotateIt()")
let newAngle = self.rotated ? 0.0 : CGFloat.pi
self.imageView.transform = CGAffineTransform(rotationAngle: newAngle)
self.rotated.toggle()
}
func colorIt() {
print("colorIt()")
let newAlpha: CGFloat = self.colored ? 1 : 0.1
let newColor = self.colored ? UIColor.clear : UIColor.green
self.imageView.alpha = newAlpha
self.imageView.backgroundColor = newColor
self.colored.toggle()
}
}
By any combination of clicking the buttons, I can only arrive at 10 configurations of the Penguin (five with CGAffineTransforms and the same five modified by the color button).
I'll use these images to answer some of your questions. I've tried to label these images "Configuration1A, Configuration2A, ... Configuration1B", where the B configurations are identical to the A configurations but with the color button's effect. Perhaps the labels will show up in my post (I'm very new to posting on StackOverflow).
Here are the five configurations with the first one repeated using the color button:
I first tried repeated button pressings. For each series of pressing a given button, I first returned the penguin to its original position, size, angle and color. For these repeated button pressings, the behavior I observed was as follows:
Button Observed behavior
scale ... steps 2 and 3, above, repeat indefinitely (WHAT???).
move Penguin moves (-50, -150), Configuration1A to Configuration3A (as expected).
move Penguin moves (50, 150) Configuration3A to Configuration1A (as expected).
move ... behavior above repeats indefinitely (as expected).
rotate Penguin rotates by pi, Configuration1A to Configuration5A (as expected).
rotate Penguin rotates by pi, Configuration5A to Configuration1A (as expected).
rotate ... behavior above repeats indefinitely (as expected).
color Penguin's color changes, Configuration1A to Configuration1B (as expected).
color Penguin's color changes, Configuration1B to Configuration1A (as expected).
color ... behavior above repeats indefinitely (as expected).
Upvotes: 2
Views: 2202
Reputation: 131491
The opposite of scale 1.5 (50% bigger) is not -1.5, it's 0.5. (50% of it's original size)
Actually, wouldn't you want it to alternate between a scale of 1.5 (50% bigger) and 1.0 (normal size?)
Assuming you do want to alternate between 50% bigger and 50% smaller, change your scaleIt function to:
func scaleIt() {
print("scaleIt()")
let newScale: CGFloat = self.scaled ? -1.5 : 0.5
self.imageView.transform = CGAffineTransform(scaleX: newScale, y: newScale)
self.scaled.toggle()
}
When you set the X or Y scale to a negative number, it inverts the coordinates in that dimension. Inverting in both dimensions will appear like a rotation.
As somebody else mentioned, the way you've written your functions, you won't be able to combine the different transforms. You might want to rewrite your code to apply the changes to the view's existing transform:
func scaleIt() {
print("scaleIt()")
// Edited to correct the math if you are applying a scale to an existing transform
let scaleAdjustment: CGFloat = self.scaled ? -1.5 : 0.66666
self.imageView.transform = self.imageView.transform.scaledBy(x: scaleAdjustment, y: scaleAdjustment)
self.scaled.toggle()
}
Note that changes to transforms are not "commutative", which is a fancy way of saying that the order in which you apply them matters. Applying a shift, then a rotate, will give different results than applying a rotate, then a shift, for example.
Another thing:
The way you've written your functions, they will set the view to its current state, and then invert that current state for next time (you toggle scaled
after deciding what to do.)
That means that for function like moveIt() nothing will happen the first time you tap the button. Rewrite that function like this:
func moveIt() {
print("I like to moveIt moveIt!")
self.moved.toggle() //Toggle the Bool first
let newX: CGFloat = self.moved ? 0 : -50
let newY: CGFloat = self.moved ? 0 : -150
self.imageView.transform = self.imageView.transform.translatedBy(x: newX, y: newY)
}
Upvotes: 1