iOS CoreAnimation(三)drawRect, CALayerDelegate

drawRect

前面文章介绍了寄宿图及相关的几个属性

除了通过 contents 设置 CGImage,还可以用 CoreGraphics 直接绘制图片。

CoreGraphics : 是一套基于C的框架,UIKit 库中所有 UI 组件都是由 CoreGraphics 绘制实现,因此它可以实现比UIKit更底层的功能。

要用 CoreGraphics 绘图,先自定义一个类, 继承 UIView,然后重写 drawRect 方法。

 

drawRect 方法文档中的一些重点如下:

UIView 默认实现为空,尽可能不要重写该方法,尽量用视图/图层。

为什么尽可能不要重写该方法呢?因为只要被调用了,就会给视图对应的 layer 分配一个相同大小的寄宿图!!!我们用 CoreGraphics 画完之后,是作为寄宿图呈现在图层上的,作为 layer.contents 保存起来,尽管保存的对象是我们不认识的 CABackingStore 类型;直到需要重新绘制或手动调用 setNeedsDisplay 重绘

当视图需要在屏幕上绘制,方法会被自动调用,不要自行调用该方法。该方法被调用时,UIKit 准备好了绘图上下文,代码里通过 UIGraphicsGetCurrentContext() 获得,不要想着持有这个 context 对象,因为其他视图 draw 方法被调用时,上下文变了。如果视图的 isOpaque 属性为 true,那自定义的 draw rect 最好也填充不透明的内容 (这个属于编码规范,不是硬性要求)。

如果的确需要重写draw rect,且继承自 UIView,可以不调用 super.draw(rect),但是如果继承自别的类,可能父类有一些自己的工作,还是要写上

1 画直线的例子

import UIKit

class CustomView: UIView {
    
    override func draw(_ rect: CGRect) {
        
        // 获取上下文,理解为 画笔
        let cgContext = UIGraphicsGetCurrentContext()
        // 一个矩形区域
        let drawRect: CGRect = frame.insetBy(dx: 100, dy: 210)
        
        let path = CGMutablePath()
        // 下面想象着一条路径的移动:move 到一个点,加一条线到一个点·····
        path.move(to: CGPoint(x: drawRect.minX, y: drawRect.minY))
        path.addLine(to: CGPoint(x: drawRect.maxX, y: drawRect.minY))
        path.addLine(to: CGPoint(x: drawRect.maxX, y: drawRect.maxY))
        path.addLine(to: CGPoint(x: drawRect.minX, y: drawRect.maxY))
        
        // 设置画笔参数,看函数名就知道意思
        cgContext?.addPath(path)

        cgContext?.setLineWidth(20)
        cgContext?.setStrokeColor(UIColor.red.cgColor)
        cgContext?.setShadow(offset: CGSize(width:3,height:3), blur: 15, color: UIColor.black.cgColor)

        // 画 = stroke
        cgContext?.strokePath()
        
        // 再画一个
        cgContext?.setStrokeColor(UIColor.green.cgColor)
        cgContext?.stroke(drawRect.insetBy(dx: 40, dy: 50))
    }
}
  
class ViewController: UIViewController {
    
    var myView: CustomView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        myView = CustomView(frame:view.bounds)
        myView.backgroundColor = UIColor(white: 0.85, alpha: 1) // 默认黑
        view.addSubview(myView)
    }
}

效果:

 

还有一些函数,列几个:

        cgContext?.setLineCap(.round)  //顶点样式

        cgContext?.setLineJoin(.round)  //连接点

        cgContext?.setShadow(offset: CGSize(width:3,height:3), blur: 10, color: UIColor.black.cgColor)   // 阴影

        cgContext?.setLineDash(phase: 0, lengths: [20,8])   // 画虚线,第一个参数写0,第二个数组是 实虚长度比例

 

2、画圆:

class CustomView: UIView {
    
    override func draw(_ rect: CGRect) {
        
        let cgContext = UIGraphicsGetCurrentContext()
        let drawRect:CGRect = frame.insetBy(dx: 50, dy: 50)
        let path = CGMutablePath()
         
        //绘制圆弧必须确定:圆心的坐标、半径、圆弧的起点角度和终点角度(弧度表示),常量 CGFloat.pi 是 π,即180。
        let radius = drawRect.width/2 //半径
        let center = CGPoint(x: drawRect.midX, y: drawRect.midX)//圆心
        
        //clockwise如果是true,画的是互补的那部分圆
        path.addArc(center: center, radius: radius, startAngle: 0, endAngle: CGFloat.pi*1.5, clockwise: true)
         
        cgContext?.setLineWidth(6)
        cgContext?.setStrokeColor(UIColor.red.cgColor)
         
        cgContext?.addPath(path)
        cgContext?.strokePath()
    }
}

绘制其他图形、其他用法,看函数名大致可以猜到,不详细介绍了。

        cgContext?.setFillColor(UIColor.red.cgColor)

        cgContext?.drawPath(using: .fillStroke)

        

        path.addRoundedRect(in: CGRect, cornerWidth: CGFloat, cornerHeight: CGFloat)

        path.addEllipse(in: CGRect)

        path.addQuadCurve(to: CGPoint, control: CGPoint)

        path.addCurve(to: CGPoint, control1: CGPoint, control2: CGPoint)

 

CALayerDelegate

前面提到,自己画的内容实际上是作为寄宿图展示的。

CALayer 有一个可选的 delegate 属性,UIView 自己的 layer 的 delegate 就是视图自己,最好不要修改,内部实现有一些额外操作;手动创建的 layer 可以指定其他对象作为 delegate;当 CALayer 对象被调用显示时,会请求它的代理(例如 UIView)给它一个寄宿图来显示。

如果实现了协议中这个方法(UIView 是实现了),执行后就完成了绘制,不再往下执行

func display(_ layer: CALayer) { }

自定义的代理可以在这个方法内直接设置 contents 属性等操作

如果没实现该方法,CALayer 会尝试调用下面这个协议方法: 

func draw(_ layer: CALayer, in ctx: CGContext) {

        //ctx.setLineWidth(10)

        //ctx.setStrokeColor(UIColor.red.cgColor)

        //ctx.strokeEllipse(in: layer.bounds)

}  

如果 delegate 对这个协议方法 也没实现,图层就尝试调用自己的 draw(in:) 方法,绘制。

UIView 在 display(_ layer) 里做了所有工作,如 是否调用 drawRect、在合适的时机调用 layer.display() 等

 

demo:


class ViewController: UIViewController, CALayerDelegate {
    
    var myView: UIView!
    /// myView 的大小
    let size: CGFloat = 200
    /// 屏幕的宽度
    let width = UIScreen.main.bounds.width
    /// 屏幕的高度
    let height = UIScreen.main.bounds.height
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        myView = UIView(frame:view.bounds)
        myView.backgroundColor = UIColor.lightGray
        
        let subLayer = CALayer()
        
        subLayer.frame = CGRect(x: size/4, y:size/4, width:size/2, height:size/2)
        subLayer.backgroundColor = UIColor(red: 0.6, green:0.3, blue: 0.2, alpha:1).cgColor
        
        subLayer.contentsScale = UIScreen.main.scale
        subLayer.delegate = self
        myView.layer.addSublayer(subLayer)
        view.addSubview(myView)
        
        // CALayer 的绘制需要手动调用 display(UIView 自己封装了自动绘制的逻辑)
        subLayer.display()
    }
    
    // 先不实现这个方法
    // func display(_ layer: CALayer) {}
     
    // 实现了这个 CALayerDelegate 协议方法
    func draw(_ layer: CALayer, in ctx: CGContext) {
        ctx.setLineWidth(10)
        ctx.setStrokeColor(UIColor.red.cgColor)
        ctx.strokeEllipse(in: layer.bounds)
    }
}

运行后发现有一处和预期不同:绘制的圆沿边界被裁剪了。但我们并没有 masksToBounds 啊?因为 CALayerDelegate 协议方法绘制时 不支持对超出边界外的内容绘制(CALayer 调用draw layer in ctx 时,ctx 只申请了 bounds 范围的位图) 

总结

根本没必要用这个协议画图,也没有必要看这节。有 UIView::drawRect,CALayerDelegate 一般用不到...

况且实现 UIView::drawRect 后,UIView 会帮你做完剩下的工作,包括在需要重绘的时候调用 CALayer::display,减少编程失误。

那为什么有 CALayerDelegate 这个东西?部分原因是,macOS 上和 iOS 上的 CALayer 是通用的,但是二者视图层交互形式不一样,于是分别有 NSView、UIView 作为不同形式交互的实现。

 

下一篇:图层几何学 之 布局与锚点 和相关应用

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值