0%

Core Graphics

反馈请联系hertz@hertzwang.com,谢谢

本文参考

iOS绘图技术(一、简单介绍和绘制视图)

iOS绘图框架CoreGraphics分析

简要说明

  • iOS 在运行循环中会整合所有的绘图请求,并一次性将它们绘制出来
  • 不能在子线程中绘制,也不能进行复杂操作,否则会造成主线程卡顿
  • 调用 UIView 的 drawRect: / draw(_:) 方法进行绘制
  • setNeedsDisplay 该方法标记为整个视图重新绘制,并且会在下一次绘制周期中重新绘制,自动调用 drawRect: / draw(_:)方法
  • setNeedsDisplayInRect: 该方法标记为视图部分区域重新绘制,周期同上。从绘制带来的开销角度,不推荐整个视图的重新绘制。

UIKit部分

UIKit是高级的图形接口,它的 API 都是基于 Objective-C的。它能够访问绘图、动画、字体、图片等内容。

填充、描边

代码示例:

override func draw(_ rect: CGRect) {
    // Drawing code
    
    // 填充
    /// 方式一
    UIColor.brown.setFill() // UIKit,将当前的图形上下文设置要填充的颜色
    UIRectFill(rect) // UIKit,填充矩形
    
    /// 方式二:路径形式填充
    let p = UIBezierPath.init(roundedRect: CGRect.init(x: 120, y: 120, width: 40, height: 40), cornerRadius: 5)
    UIColor.red.setFill()
    p.fill()
    
    // 描边
    /// 方式一
    UIColor.white.setStroke() // UIKit,描边
    let frame = CGRect.init(x: 20, y: 30, width: 100, height: 300)
    UIRectFrame(frame)
    
    /// 方式二:路径形式描边
    let b = UIBezierPath.init(roundedRect: CGRect.init(x: 120, y: 170, width: 40, height: 40), cornerRadius: 5)
    UIColor.red.setStroke()
    b.stroke()
    
    
    // 画横线
    let space: CGFloat = 44.0
    var lineFrame = CGRect.init(x: 0, y: 350, width: rect.width, height: 1)
    UIColor.green.setFill()
    
    for _ in 1...4 {
        UIRectFill(lineFrame)
          //UIRectFrame(lineFrame)
        
        lineFrame.origin.y += space
    }
    
}

仅供参考的个人理解:

  • 简单矩形填充可用 setFillUIRectFill 结合使用
  • 简单矩形描边可用 setStrokeUIRectFrame 结合使用
  • 复杂图像可以用 UIBezierPath 设置路径来进行填充、描边
  • UIBezierPath 比较灵活一些

UIImage绘制图像、NSString绘制文本

代码示例:

override func draw(_ rect: CGRect) {
    // Drawing code
    
    // UIImage绘制图像
    if let image = UIImage.init(named: "dog") {
        let imageReact = CGRect.init(origin: CGPoint.init(x: 150, y: 150), size: image.size)
        image.draw(in: imageReact) // 注意图片的原始size
        //image.draw(in: imageReact, blendMode: .normal, alpha: 0.9)
        //image.draw(at: imagePoint) // 从某个点开始
        //image.draw(at: imagePoint, blendMode: .normal, alpha: 1.0)
    }
    
    // NSString绘制文字
    let dogString = NSString.init(string: "小狗")
    let font = UIFont.systemFont(ofSize: 34)
    
    let attr = [NSAttributedStringKey.font : font]
    
    dogString.draw(at: CGPoint.init(x: 150, y: 150), withAttributes: attr)
     //dogString.draw(in: <#T##CGRect#>, withAttributes: <#T##[NSAttributedStringKey : Any]?#>)
}

仅供参考的个人理解:

  • 绘制图片、文本,可以设置起点,也可以设置size
  • 图片绘制需要考虑原始的size

实战

九九乘法表

参考代码如下,如有更好的代码请联系我,非常感谢!

class MultiplicationTableView: UIView {
    
    override func draw(_ rect: CGRect) {
        // Drawing code
        
        let width = UIScreen.main.bounds.width
        let height = width * 3.0 / 5.0 // 5:3
        let itemWidth = (width - 20.0) / 9.0
        let itemHeight = height / 9.0
        
        // 设置背景
        UIColor.white.setFill()
        UIRectFill(rect)

        // 乘法表内容
        var strRect = CGRect.zero
        var strAttr = [NSAttributedStringKey:Any]()
        strAttr[NSAttributedStringKey.font] = UIFont.systemFont(ofSize: 7)
        
        let style = NSMutableParagraphStyle()
        style.alignment = .center
        strAttr[NSAttributedStringKey.paragraphStyle] = style
    
        for x in 1...9 {
            for y in 1...9 {
                guard y <= x else {
                    break
                }
                let stringChinese = NSString.init(string: "\(y.chinese)\(x.chinese)\((x*y).resultChinese)")
                let string = NSString.init(string: "\(y)x\(x)=\(x*y)\n\(stringChinese)")
                debugPrint("\(string)")
                
                strRect = CGRect.init(x: CGFloat(y-1) * itemWidth - CGFloat(y), y: CGFloat(x-1) * itemHeight - CGFloat(x), width: itemWidth, height: itemHeight)
                
                // 背景
                UIColor.init(red: CGFloat(150+y*10)/255.0, green: CGFloat(100+y*10)/255.0, blue: CGFloat(60+y*10)/255.0, alpha: 1.0).setFill()
                UIRectFill(strRect)
                
                // 内容
                let strSize = string.size(withAttributes: strAttr)
                let topMargin = (itemHeight - strSize.height) * 0.5
                let stringRect = CGRect.init(x: strRect.minX, y: strRect.minY + topMargin, width: itemWidth, height: itemHeight)
                string.draw(in: stringRect, withAttributes: strAttr)
                
                // 边框
                UIRectFrame(strRect)
            }
        }
    }
}

extension Int {
    var chinese: String {
        get {
            switch self {
            case 1:
                return "一"
            case 2:
                return "二"
            case 3:
                return "三"
            case 4:
                return "四"
            case 5:
                return "五"
            case 6:
                return "六"
            case 7:
                return "七"
            case 8:
                return "八"
            case 9:
                return "九"
            default:
                return ""
            }
        }
    }
    
    var resultChinese: String {
        get {
            
            if self < 10 {
                // 小于10: 得X
                return "得\(self.chinese)"
            } else {
                if self%10 == 0 {
                    // 个位为0: X十
                    return "\((self/10).chinese)十"
                } else if self < 20 {
                    // 十位为1:十X
                    return "十\((self-10).chinese)"
                } else {
                    // X十Y
                    let unit = self%10
                    let decade = (self-unit)/10
                    return "\(decade.chinese)十\(unit.chinese)"
                }
            }
        }
    }
}

随机验证码

参考代码如下,如有更好的代码请联系我,非常感谢!

//
//  VerificationCodeView.swift
//  CoreGraphicsDemo01
//
//  Created by HertzWang on 2018/1/19.
//  Copyright © 2018年 HertzWang. All rights reserved.
//

import UIKit

class VerificationCodeView: UIView {
    private let items = ["0","1","2","3","4","5","6","7","8","9"]
    
    private let count: Int = 4
    
    override func draw(_ rect: CGRect) {
        // Drawing code
        
        // 设置背景
        UIColor.init(red: CGFloat(arc4random_uniform(UInt32(80)))/255.0, green: CGFloat(arc4random_uniform(UInt32(125)))/255.0, blue: CGFloat(arc4random_uniform(UInt32(255)))/255.0, alpha: 1.0).setFill()
        UIRectFill(rect)
        
        // 设置内容
        let itemWidth = rect.width / CGFloat(count)
        for index in 0..<count {
            let random = Int(arc4random_uniform(UInt32(self.items.count)))
            let item = NSString.init(string: self.items[random])
            let attr = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 18),
                        NSAttributedStringKey.foregroundColor: UIColor.init(red: CGFloat(arc4random_uniform(UInt32(80)))/255.0, green: CGFloat(arc4random_uniform(UInt32(125)))/255.0, blue: CGFloat(arc4random_uniform(UInt32(255)))/255.0, alpha: 1.0) ]
            
            var point = CGPoint.init(x: itemWidth * CGFloat(index), y: 0)
            point.x += CGFloat(arc4random_uniform(UInt32(itemWidth-18)))
            point.y += CGFloat(arc4random_uniform(UInt32(rect.height-18)))
            
            item.draw(at: point, withAttributes: attr)
            
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        addGestureRecognizer(UITapGestureRecognizer.init(target: self, action: #selector(refreshCode)))
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    @objc private func refreshCode() {
        setNeedsDisplay()
    }
}

进阶

上述的操作只能在 UIView 的 drawRect: / draw(_:) 中绘制,现在说下在其它位置的写法

图片绘制

示例代码:

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    
    // 绘制图片
    /// 1. 获取图片上下文
    UIGraphicsBeginImageContextWithOptions(CGSize.init(width: 100, height: 100), false, 0) // size 可用理解为画布
    /// 2. 绘制
    let p = UIBezierPath.init(ovalIn: CGRect.init(x: 0, y: 0, width: 100, height: 100)) // 设置路径,在画布中的frame
    UIColor.red.setFill() // 设置填充颜色
    p.fill() // 填充
    
    /// 3.1. 从上下文中获取绘制的图片
    if let image = UIGraphicsGetImageFromCurrentImageContext() {
        /// 3.2. 添加到视图上
        let imageView = UIImageView.init(image: image)
        imageView.frame = CGRect.init(origin: CGPoint.zero, size: image.size)
        self.view.addSubview(imageView)
    }
    
    /// 4. 关闭上下文
    UIGraphicsEndImageContext()
}

Core Graphics/Quartz 2D 部分

drawRect:

示例代码:

override func draw(_ rect: CGRect) {
    // Drawing code
    
    // Core Graphics
    if let con = UIGraphicsGetCurrentContext() { // 获取上下文(画布)
        /// 矩形填充
        con.addRect(CGRect.init(x: 120, y: 100, width: 100, height: 100))
        con.setFillColor(UIColor.green.cgColor)
        con.fillPath()
        
        /// 圆形描边
        con.addEllipse(in:CGRect.init(x: 120, y: 300, width: 100, height: 100))
        con.setStrokeColor(red: 255, green: 0, blue: 0, alpha: 1)
        con.strokePath()
    }
}

图片上下文

示例代码:

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    
    // 使用 Core Graphics 绘制图片
    /// 1. 获取上下文
    UIGraphicsBeginImageContext(CGSize.init(width: 100, height: 100)) // 画布size,超出部分不显示
    /// 2. 绘制图片
    if let con = UIGraphicsGetCurrentContext() {
          //con.addEllipse(in: CGRect.init(x: 0, y: 0, width: 100, height: 100)) // 圆形
        con.addRect(CGRect.init(x: 0, y: 0, width: 100, height: 100)) // 矩形
        con.setFillColor(UIColor.green.cgColor)
        con.fillPath()
        /// 3.1 获取图片
        if let image = UIGraphicsGetImageFromCurrentImageContext() {
            /// 3.2 添加到视图
            let imageView = UIImageView.init(image: image)
            imageView.frame = CGRect.init(origin: CGPoint.zero, size: image.size)
            self.view.addSubview(imageView)
        }
    }
    
    /// 4. 关闭图片上下文
    UIGraphicsEndImageContext()
}