iOS CoreAnimation (五) convert, hitTest, contains, zPosition

几个方法和属性贯穿本文

1、isGeometryFlipped

iOS 坐标从左上角开始,如果有过桌面开发经验的话,可以发现桌面端是从左下角开始。设置一个属性:

layer.isGeometryFlippedtrue

让坐标系从左下角开始,左下角坐标变为(0,0)。这个属性有什么用呢?假如有一个 macOS 程序,后来想迁移到 iPad 上,其中用到了 drawRect,为了能直接 copy 代码,发现有这个翻转坐标系属性,移植代码简单了很多。

2、convert 系列方法

某个图层坐标系下的点坐标,转换成另一个图层坐标系下的坐标。有四个 convert 方法,看参数名可以知道功能。

例如 layer.convert(CGPoint, to:CALayer)   ,第一个参数是调用者坐标系下的某个点,返回该点在第二个参数 layer 坐标系下的坐标。后面 demo 还介绍了一个。

3、contains

判断图层是否包含某一个点 CGPoint,返回 Bool

CALayer::contains(CGPoint) 实际会返回 layer 的 bounds 是否包含该点。所以用这个方法要小心:乍一看某个点(-20,-20)不可能在 layer 上,但是!!!它可能被 layer 的 bounds 包含!!!

4、zPosition

Z坐标轴。和 UIView 严格的二维坐标系不同,CALayer 有一个 Z 轴,除了做3D动画(以后会介绍)外,这个 zPosition 属性唯一的作用是,调整同级图层的渲染顺序。这里的同级怎么理解呢,,,视图树同时对应着一个图层树,假设 view1 和 view2 是同级的兄弟关系,那么它俩的 layer 也是同级的;view1 的 layer 的 subLayers,子图层之间是同级的,但子图层和 view1、view2 的 layer 不是同级的,和根 view 的 layer 更不是同级的。。。zPosition 默认0,值大的,盖在同级图层的最上面。

5、Hit Testing

CALayer 是不处理交互的,在交互事件传递中,用到的是 UIView 的 hitTest,复习响应者链。那为什么 CALayer 还要提供这个方法呢?可能是预留给开发者自定义一套事件处理机制吧。。

一个简单的 demo 是,如图一个 UIView,其 layer(蓝色)有一个subLayer(绿色),我要实现:

触摸蓝色部分,打印 blue ,触摸 绿色部分,打印 green。

 

第一版代码如下:

class ViewController:UIViewController,CALayerDelegate {
    
    var blueView: UIView!
    var greenLayer:CALayer!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        blueView = UIView(frame:CGRect(x: 50, y: 200, width: 350, height: 300))
        blueView.backgroundColor = UIColor.blue
          
        greenLayer = CALayer()
        greenLayer.frame = CGRect(x: 50, y:200, width: 200, height:100)
        greenLayer.backgroundColor = UIColor.green.cgColor
         
        blueView.layer.addSublayer(greenLayer)
        view.addSubview(blueView)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // 取出 touch
        let allTouches = touches as NSSet
        let touch = allTouches.anyObject() as! UITouch
        // 得到 touch 的 point,注意是相对于 self.view
        var point = touch.location(in: self.view)
        // point 是触摸位置在 self.view 坐标系中的位置
        // 调用 convert 转换坐标系,根据函数名确定转换方式
        point = blueView.layer.convert(point, from: view.layer)
        // point 假设值为(50,50),convert 方法从 from  参数 view 的坐标系出发,找到此坐标系中的 (50,50) 的点,然后方法调用者 blueView.layer  用自己的坐标系衡量刚找到的点,返回。如果返回的点在 blueView.layer 的 bounds 内,即 contains 方法:
        if blueView.layer.contains(point) {
            // 此时确定了在蓝色范围内,然后要判断,是否是在绿色范围内
            point = greenLayer.convert(point, from:blueView.layer)
            if greenLayer.contains(point) {
                print(" in green")
            } else {
                print("in blue")
            }
        }
    } 
}

上面的处理挺麻烦的,可以用 hitTest 方法代替,方法接受一个 CGPoint 类型参数,它返回一个包含这个坐标点的图层树上最远的图层,或空。这意味着不再需要 contains 那样人工在每个子图层变换坐标。响应者链的文章里详细说了 UIView::hitTest,CALayer 的函数思路是一样的,不再赘述 

 override func touchesBegan(_ touches:Set<UITouch>, with event:UIEvent?) {
        let point = touches.first!.location(in: view)
        
        let layer = view.layer.hitTest(point)
        
        if layer == blueView.layer {
            print(1)
        } else if layer == greenLayer {
            print(2)
        }
        
        // 强调:之前提到的 zPosition 属性可以改变图层的渲染顺序,但不能改变事件传递的顺序。
        // 因此如果改变图层的 z轴 顺序,会无法检测到看起来的“最前方”的视图点击事件,这是因为在图层树上实际仍被另一个图层遮盖住了!
    }

6、CALayer 的自动布局

在 iOS 中,我们平时写自动布局的代码时,都是给 UIView 加约束或者用 UIViewAutoresizingMask。

经常用到一些 CALayer 的子类,比如 CAGradientLayer 作为一个 view 的子图层加个渐变色。

如果你的 app 想适配屏幕大小变化,比如转屏、iPad 分屏,就会发现,UIView 可以自动布局、自适应宽高,但是那个子图层得手动更新 frame,咋办呢?

一般情况下,都是自定义 view 重写 layoutSubviews,然后指定子图层的 frame 为 self.bounds。

其实还有一个函数,做这件事看起来更“专业”:CALayerDelegate 的 layoutSublayers(of: CALayer) 方法:

class AutoLayoutSublayersView: UIView {
    
    override func layoutSublayers(of layer: CALayer) {
        super.layoutSublayers(of: layer)
        
        layer.sublayers?.forEach({ subLayer in
            subLayer.frame = layer.bounds
        })
        
    }
}

UIView 可以自动布局,管理着自己的 layer 的大小,UIView 遵守 CALayerDelegate(view 自己的 layer 的 delegate 只能是该 view),layer 大小变化*时,回调 delegate,调用上面的代码,我们把更新子图层大小的代码写在这里。

*回调时机?我们熟悉 layoutSubviews 的调用时机,实际上,layoutSubviews 是在 layoutSublayers(of: CALayer) 里被调用的!!!

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值