iOS CGAffineTransform学习

1,787 阅读3分钟

iOS CGAffineTransform

仿射变换


    var containerView: UIView = {
        let v = UIView()
        v.backgroundColor = .green

        //是否渲染背面,在3d变换的时候如果沿x或y轴旋转180度,就转到了view的背面
        //当true时会展示一个镜像的view
        //false时不渲染,不显示
        v.layer.isDoubleSided = true
        return v
    }()

    containerView.frame = CGRect(x: 100, y: 100, width: 200, height: 200)
    let affine = CGAffineTransform(rotationAngle: .pi / 4)
    //layer的affineTransform对应view的transform
    containerView.layer.setAffineTransform(affine)
    view.addSubview(containerView)

CGAffineTransform是一个结构体


public struct CGAffineTransform {

    public var a: CGFloat

    public var b: CGFloat

    public var c: CGFloat

    public var d: CGFloat

    public var tx: CGFloat

    public var ty: CGFloat

    public init()

    public init(a: CGFloat, b: CGFloat, c: CGFloat, d: CGFloat, tx: CGFloat, ty: CGFloat)
}

结构体是值引用,放在栈空间,使用let赋值就不可能修改,所以可以在初始化的时候使用链式语法的调用来初始化一个包含平移,旋转,缩放的对象


let affine = CGAffineTransform(rotationAngle: .pi / 4).scaledBy(x: 2, y: 2).translatedBy(x: 100, y: 100)

因为scaledBy()函数是不开源的,但是根据函数返回的是CGAffineTransform,可以猜到函数就是一个初始化的过程,这里用这个demo来实验

struct ZPHTest {
    var x: Int = 0
    var y: Int = 0
}

extension ZPHTest {
    func rotaion(angle: Int) -> ZPHTest {
        return ZPHTest(x: self.x, y: angle)
    }

    func scale(angle: Int) -> ZPHTest {
        return ZPHTest(x: angle, y: self.y)
    }
}

let t = ZPHTest(x: 10, y: 10).rotaion(angle: 20)
print(t) // ZPHTest(x: 10, y: 20)

// 大致是这么个效果

3D变换


    var trans = CATransform3DIdentity
    trans.m34 = -1.0 / 800.0
    trans = CATransform3DRotate(trans, .pi / 4, 0, 1, 0)
    containerView.layer.transform = trans

在变换的时候会有一个从远到近的视觉效果,叫做灭点,对应Transform3D的属性m34.

背面

当rotate为.pi/2的时候,也就是90度,从我们的视角看应该是看不到视图的,因为view没有厚度,当rotate为.pi的时候,也就是转到了背面,这些我们看到的是view的镜像,可以使用isDoubleSided禁止背面的渲染

transform的实际位置

layer的transform变化,frame当然变化了.

旋转的简单实现


var v1: UIImageView = {
    let v = UIImageView(frame: CGRect(x: 100, y: 100, width: 200, height: 200))
    v.image = UIImage(named: "321")
    return v
    }()

var v2: UIImageView = {
    let v = UIImageView(frame: CGRect(x: 100, y: 100, width: 200, height: 200))
    v.image = UIImage(named: "123")
    return v
}()

override func viewDidLoad() {
    super.viewDidLoad()

    view.addSubview(v1)
    let animation = CABasicAnimation()
    animation.keyPath = "transform.rotation.y"
    animation.fromValue = 0
    animation.toValue = Double.pi / 2
    animation.duration = 2
    animation.delegate = self
    animation.repeatCount = 1
    animation.setValue("v1", forKey: "v1")
    v1.layer.add(animation, forKey: nil)
}

func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {

    if anim.value(forKey: "v1") as? String == "v1" {
        print("animationDidStop")
        v1.removeFromSuperview()

        v2.layer.transform = CATransform3DMakeRotation(-.pi / 2, 0, 1, 0)
        view.addSubview(v2)
        let animation = CABasicAnimation()
        animation.keyPath = "transform.rotation.y"
        animation.fromValue = -Double.pi / 2
        animation.toValue = 0
        animation.duration = 2
        animation.delegate = self
        animation.repeatCount = 1
        v2.layer.add(animation, forKey: nil)
    } else {
        v2.layer.transform = CATransform3DMakeRotation(0, 0, 1, 0)
    }
}

题外

看3d变换本来是因为有个卡片旋转背面的需求,在看了一些文章后发现其实系统已经给出了相应的api[😂]


/// UIView的类方法
/// 从正面旋转到背面
@available(iOS 4.0, *)
    open class func transition(from fromView: UIView, to toView: UIView, duration: TimeInterval, options: UIView.AnimationOptions = [], completion: ((Bool) -> Void)? = nil)

使用这个方法.就不用自己去计算旋转的角度等等了.iOSNB!


参考

iOS核心动画高级技巧