首页 > 解决方案 > 为什么 CGAffineTransform(scaleX:y:) 会导致我的视​​图像调用 CGAffineTransform(rotationAngle:) 一样旋转?

问题描述

我学习了 Paul Hudson 的(Hacking with Swift)动画教程(他的项目 15)并尝试扩展它以查看多个动画相互叠加的效果。

有四种不同的动画:缩放、平移、旋转和颜色。在他的教程中,我使用了四个按钮来允许单独选择每个动画,而不是像他的教程中那样循环遍历所有动画。我还通过反转原始动画而不是使用 CGAffineTransform.identity 来修改他的“撤消”动画,因为身份转换会撤消所有动画到该点。

我的问题是,当我单击我的 c 按钮时,它会在第一次单击时进行适当的缩放,但是,它不会在第二次单击时将企鹅缩放回其原始大小,而是旋转视图。随后单击缩放按钮继续旋转视图,就像我单击旋转按钮一样。

还有其他异常情况,例如第一次单击移动按钮会适当地移动企鹅,但随后单击旋转按钮会旋转企鹅并将其移回原始位置。我不确定它为什么会向后移动,但我接受这可能是我自己对动画系统的无知。

我添加了打印语句并放入断点来调试比例问题。代码中的所有内容似乎都完全按照编码工作,但动画不符合逻辑!任何帮助,将不胜感激。

完整的程序比较简单:

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()
    }
    
}

通过单击按钮的任意组合,我只能得到 Penguin 的 10 种配置(其中 5 种带有 CGAffineTransforms 和相同的 5 种由颜色按钮修改)。

我将使用这些图像来回答您的一些问题。我尝试将这些图像标记为“Configuration1A,Configuration2A,... Configuration1B”,其中 B 配置与 A 配置相同,但具有颜色按钮的效果。也许标签会出现在我的帖子中(我对在 StackOverflow 上发帖很陌生)。

以下是使用颜色按钮重复第一个配置的五种配置:

配置1A

配置2A

配置3A

配置4A

配置5A

配置 1B(与 1A 相同,但带有颜色按钮)

我首先尝试重复按下按钮。对于每一系列按下给定按钮,我首先将企鹅恢复到其原始位置、大小、角度和颜色。对于这些重复的按钮按下,我观察到的行为如下:

按钮观察到的行为

  1. scale Penguin 从 Configuration1A 缩放到 Configuration2A(如预期的那样)。
  2. scale Penguin 从 Configuration2A 旋转到 Configuration4A(什么???)。
  3. scale Penguin 以 pi 从 Configuration4A 旋转到 Configuration2A(什么???)。

规模......上面的第 2 步和第 3 步,无限重复(什么???)。

  1. move Penguin 移动 (-50, -150),Configuration1A 到 Configuration3A(如预期的那样)。

  2. move Penguin 将 (50, 150) Configuration3A 移动到 Configuration1A(如预期的那样)。

  3. move ... 上述行为无限重复(如预期的那样)。

  4. rotate Penguin 旋转 pi,Configuration1A 到 Configuration5A(如预期的那样)。

  5. rotate Penguin 旋转 pi,Configuration5A 到 Configuration1A(如预期的那样)。

  6. rotate ... 上述行为无限重复(如预期的那样)。

  7. color Penguin 的颜色变化,Configuration1A 到 Configuration1B(如预期的那样)。

  8. color Penguin 的颜色变化,Configuration1B 到 Configuration1A(如预期的那样)。

  9. color ... 上述行为无限重复(如预期的那样)。

标签: iosswiftuiviewanimationcgaffinetransform

解决方案


比例 1.5(大 50%)的反面不是 -1.5,而是 0.5。(原尺寸的 50%)

实际上,您不希望它在 1.5(大 50%)和 1.0(正常大小)之间交替吗?

假设您确实想要在大 50% 和小 50% 之间交替,请将 scaleIt 函数更改为:

func scaleIt() {
    print("scaleIt()")
    let newScale: CGFloat = self.scaled ? -1.5 : 0.5
    self.imageView.transform = CGAffineTransform(scaleX: newScale, y: newScale)
    self.scaled.toggle()
}

当您将 X 或 Y 比例设置为负数时,它会反转该维度中的坐标。两个维度的反转看起来就像一个旋转。

正如其他人所提到的,您编写函数的方式将无法组合不同的转换。您可能需要重写代码以将更改应用于视图的现有转换:

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()
}

编辑:

请注意,对变换的更改不是“可交换的”,这是一种奇特的说法,即应用它们的顺序很重要。例如,先应用移位,然后再旋转,将产生与应用旋转再旋转不同的结果。

编辑#2:

另一件事:

您编写函数的方式,它们会将视图设置为其当前状态,然后为下一次反转该当前状态(您scaled在决定要做什么后切换。)

这意味着对于 moveIt() 之类的函数,在您第一次点击按钮时不会发生任何事情。像这样重写该函数:

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)
}

推荐阅读