iOS开发基础32-Quartz2D(二)绘图实战:画线、饼图、雪花动画、水印、裁剪与截图

Quartz 2D 绘图实战:画线、饼图、雪花动画、水印、裁剪与截图

本文通过 12 个实战案例,系统掌握 Quartz 2D 与 UIKit 绘图:直线/曲线/扇形绘制、setNeedsDisplay 重绘机制、下载进度圆、饼图、文字与图片绘制、CADisplayLink 雪花动画、上下文状态栈、矩阵变换、图片水印、圆形裁剪带圆环、屏幕截图、区域截图、图片擦除。


一、画线

1. 一条直线

绘图必须在 drawRect: 中进行,只有在此方法中才能获取与 View 关联的图形上下文。

- (void)drawRect:(CGRect)rect {
    // 1. 获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 2. 拼接路径(UIBezierPath 封装了常用路径)
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(50, 50)];      // 起点
    [path addLineToPoint:CGPointMake(200, 200)];  // 终点
    
    // 3. 将路径添加到上下文
    CGContextAddPath(ctx, path.CGPath);
    
    // 4. 渲染(描边)
    CGContextStrokePath(ctx);
}

UIBezierPath 可直接调用 stroke / fill,无需手动 CGContextAddPath + CGContextStrokePath:

UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(50, 50)];
[path addLineToPoint:CGPointMake(200, 200)];
[path stroke];  // 直接描边

2. 两条不相连的线

一个 UIBezierPath 可保存多条线段。若线段不相连,重新调用 moveToPoint: 设置新起点:

- (void)drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 第一条线
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(10, 10)];
    [path addLineToPoint:CGPointMake(20, 20)];
    CGContextAddPath(ctx, path.CGPath);
    
    // 第二条线(不相连,重新设置起点)
    path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(50, 50)];
    [path addLineToPoint:CGPointMake(20, 200)];
    CGContextAddPath(ctx, path.CGPath);
    
    CGContextStrokePath(ctx);
}

不相连的线段建议各自使用一个路径对象,逻辑更清晰。

3. 绘制曲线(二次贝塞尔)

- (void)drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(10, 125)];
    // 二次贝塞尔曲线:终点 + 控制点
    [path addQuadCurveToPoint:CGPointMake(240, 125) controlPoint:CGPointMake(125, 240)];
    [path addLineToPoint:CGPointMake(10, 125)];
    
    CGContextAddPath(ctx, path.CGPath);
    
    // 设置绘图状态(必须在渲染之前)
    [[UIColor redColor] setStroke];
    CGContextSetLineWidth(ctx, 15);
    CGContextSetLineCap(ctx, kCGLineCapRound);    // 圆角端点
    CGContextSetLineJoin(ctx, kCGLineJoinRound);   // 圆角连接
    
    CGContextStrokePath(ctx);
}

4. 画扇形

- (void)drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    CGPoint center = CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * 0.5);
    // 圆弧:圆心、半径、起始角、结束角、是否顺时针
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
                                                          radius:100
                                                      startAngle:0
                                                        endAngle:M_PI_2
                                                       clockwise:YES];
    [path addLineToPoint:center];  // 连接到圆心
    [path closePath];              // 关闭路径(从终点连回起点)
    
    CGContextAddPath(ctx, path.CGPath);
    
    [[UIColor redColor] setFill];
    [[UIColor greenColor] setStroke];
    CGContextSetLineWidth(ctx, 5);
    
    // 同时填充和描边
    CGContextDrawPath(ctx, kCGPathFillStroke);
}

fill 会自动关闭路径(即使未调用 closePath);stroke 不会自动关闭,需手动 closePath 才会绘制最后一条边。

042158257834536


二、重绘机制与下载进度

042132519861120

drawRect: 只在 View 首次显示时调用一次。如需更新绘制,必须调用 setNeedsDisplay 标记重绘,系统在下一次屏幕刷新时自动调用 drawRect:。

自定义进度 View

@interface ProgressView : UIView
@property (nonatomic, assign) CGFloat progress; // 0.0 ~ 1.0
@end

@implementation ProgressView

- (void)setProgress:(CGFloat)progress {
    _progress = progress;
    // 不能手动调用 drawRect:,必须通过 setNeedsDisplay 触发
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {
    CGPoint center = CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.width * 0.5);
    CGFloat radius = self.bounds.size.width * 0.5 - 2;
    CGFloat startA = -M_PI_2;                           // 从 12 点钟方向开始
    CGFloat endA = -M_PI_2 + _progress * M_PI * 2;     // 根据进度计算结束角
    
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
                                                          radius:radius
                                                      startAngle:startA
                                                        endAngle:endA
                                                       clockwise:YES];
    path.lineWidth = 5;
    [[UIColor blueColor] setStroke];
    [path stroke];
}

@end

控制器中使用

- (IBAction)sliderValueChanged:(UISlider *)sender {
    self.label.text = [NSString stringWithFormat:@"%.2f%%", sender.value * 100];
    self.progressView.progress = sender.value; // 内部触发 setNeedsDisplay
}

setNeedsDisplay 底层不会立即调用 drawRect:,而是给控件绑定一个"需要重绘"的标识,每次屏幕刷新(约 60 次/秒)时统一重绘所有绑定了标识的控件。


三、画饼图

042138130173311

@implementation PieView

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self setNeedsDisplay]; // 点击重新绘制(随机颜色)
}

- (void)drawRect:(CGRect)rect {
    NSArray *data = @[@25, @25, @20, @30]; // 各部分占比(总和 100)
    
    CGPoint center = CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * 0.5);
    CGFloat radius = self.bounds.size.width * 0.5;
    CGFloat startA = 0;
    CGFloat endA = 0;
    CGFloat angle = 0;
    
    for (NSNumber *num in data) {
        startA = endA;
        angle = [num intValue] / 100.0 * M_PI * 2; // 占比对应的弧度
        endA = startA + angle;
        
        UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
                                                              radius:radius
                                                          startAngle:startA
                                                            endAngle:endA
                                                           clockwise:YES];
        [path addLineToPoint:center];
        
        [[self randomColor] set]; // set 同时设置描边和填充颜色
        [path fill];
    }
}

- (UIColor *)randomColor {
    CGFloat r = arc4random_uniform(256) / 255.0;
    CGFloat g = arc4random_uniform(256) / 255.0;
    CGFloat b = arc4random_uniform(256) / 255.0;
    return [UIColor colorWithRed:r green:g blue:b alpha:1];
}

@end

四、UIKit 绘图(文字与图片)

1. 绘制文字(富文本属性)

- (void)drawRect:(CGRect)rect {
    NSString *str = @"hello!";
    
    NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
    attrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:50];
    attrs[NSStrokeWidthAttributeName] = @1;                  // 描边宽度
    attrs[NSStrokeColorAttributeName] = [UIColor redColor];  // 描边颜色
    attrs[NSForegroundColorAttributeName] = [UIColor redColor]; // 文字颜色
    
    NSShadow *shadow = [[NSShadow alloc] init];
    shadow.shadowColor = [UIColor yellowColor];
    shadow.shadowOffset = CGSizeMake(10, 10);
    shadow.shadowBlurRadius = 5;
    attrs[NSShadowAttributeName] = shadow;
    
    [str drawAtPoint:CGPointZero withAttributes:attrs];
}

2. 绘制图片与裁剪

- (void)drawRect:(CGRect)rect {
    UIImage *image = [UIImage imageNamed:@"001"];
    
    // 裁剪:必须在绘制之前设置,超出裁剪区域的内容被裁掉
    UIRectClip(CGRectMake(0, 0, 50, 50));
    
    // 按原图尺寸绘制
    [image drawAtPoint:CGPointZero];
    
    // 拉伸到指定区域绘制
    // [image drawInRect:CGRectMake(0, 0, 100, 100)];
    
    // 平铺绘制
    // [image drawAsPatternInRect:rect];
}

042142171735786

需要频繁重绘的动画,推荐使用 CADisplayLink 而非 NSTimer:CADisplayLink 与屏幕刷新同步(60 次/秒),不会出现延迟或掉帧。

@implementation SnowView

- (void)awakeFromNib {
    // CADisplayLink:屏幕每次刷新时调用(约 60 次/秒)
    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(setNeedsDisplay)];
    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    
    // 停止时调用 [link invalidate];
}

- (void)drawRect:(CGRect)rect {
    static CGFloat snowY = 0;
    
    UIImage *image = [UIImage imageNamed:@"雪花"];
    [image drawAtPoint:CGPointMake(0, snowY)];
    
    snowY += 10;
    if (snowY > rect.size.height) {
        snowY = 0;
    }
}

@end

NSTimer 受 RunLoop 模式影响,在 UIScrollView 滚动时会暂停;CADisplayLink 同样受 RunLoop 模式影响,需添加到 NSRunLoopCommonModes 才能在滚动时继续执行。


六、图形上下文状态栈

CGContextSaveGState 保存当前上下文状态(颜色、线宽、变换矩阵等),CGContextRestoreGState 恢复,用于临时修改状态而不影响后续绘制:

- (void)drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 第一条线
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(10, 125)];
    [path addLineToPoint:CGPointMake(240, 125)];
    CGContextAddPath(ctx, path.CGPath);
    
    CGContextSaveGState(ctx); // 保存默认状态
    
    [[UIColor redColor] set];
    CGContextSetLineWidth(ctx, 20);
    CGContextStrokePath(ctx);
    
    // 第二条线
    path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(125, 10)];
    [path addLineToPoint:CGPointMake(125, 240)];
    CGContextAddPath(ctx, path.CGPath);
    
    CGContextRestoreGState(ctx); // 恢复到保存时的状态(红色和20线宽被撤销)
    
    CGContextStrokePath(ctx); // 使用默认状态(黑色、1pt 线宽)绘制
}

Save 与 Restore 必须成对出现,保存几次就要恢复几次。

042142582983456


七、矩阵操作(CTM)

CTM(当前变换矩阵)作用于上下文所有后续绘制,支持平移、旋转、缩放:

- (void)drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 矩阵操作顺序很重要:先平移再旋转 ≠ 先旋转再平移
    CGContextTranslateCTM(ctx, 150, 200); // 平移
    CGContextRotateCTM(ctx, M_PI_4);       // 旋转 45°
    CGContextScaleCTM(ctx, 0.5, 0.5);      // 缩放 0.5 倍
    
    // 绘制一个椭圆(以原点为中心,配合平移使用)
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-50, -100, 100, 200)];
    [[UIColor redColor] set];
    [path fill];
}

矩阵变换是累积的,且作用于后续所有绘制。如需临时变换,配合 CGContextSaveGState/RestoreGState 使用。


八、图片水印

利用位图图形上下文,将文字或 Logo 绘制到图片上,生成新图片:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIImage *image = [UIImage imageNamed:@"小黄人"];
    
    // 1. 开启位图上下文
    // size: 上下文尺寸;opaque: 是否不透明;scale: 0 自动取屏幕 scale
    UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
    
    // 2. 绘制原图
    [image drawAtPoint:CGPointZero];
    
    // 3. 绘制水印文字
    NSString *str = @"超神五杀怪我咯";
    [str drawAtPoint:CGPointZero withAttributes:@{NSForegroundColorAttributeName: [UIColor redColor]}];
    
    // 4. 从上下文生成新图片
    UIImage *watermarkedImage = UIGraphicsGetImageFromCurrentImageContext();
    
    // 5. 结束上下文
    UIGraphicsEndImageContext();
    
    // 6. 保存到沙盒(PNG 无损,JPG 有损)
    NSData *data = UIImageJPEGRepresentation(watermarkedImage, 0.8);
    NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"watermark.jpg"];
    [data writeToFile:path atomically:YES];
}

UIGraphicsBeginImageContext(无 Options)默认 scale=1,在 Retina 屏上模糊;推荐始终使用 UIGraphicsBeginImageContextWithOptions 并设 scale=0。

042149342675519


九、圆形裁剪(带圆环)

042151136425659

通过分类方法,生成带圆环的圆形头像图片:

@interface UIImage (Circle)
+ (instancetype)circleImageWithName:(NSString *)name
                          borderColor:(UIColor *)color
                          borderWidth:(CGFloat)border;
@end

@implementation UIImage (Circle)

+ (instancetype)circleImageWithName:(NSString *)name
                          borderColor:(UIColor *)color
                          borderWidth:(CGFloat)border {
    UIImage *image = [UIImage imageNamed:name];
    
    // 上下文尺寸 = 图片尺寸 + 2 倍边框宽度
    CGFloat contextW = image.size.width + 2 * border;
    CGFloat contextH = image.size.height + 2 * border;
    CGSize contextSize = CGSizeMake(contextW, contextH);
    
    UIGraphicsBeginImageContextWithOptions(contextSize, NO, 0);
    
    // 1. 画大圆(作为圆环)
    UIBezierPath *bigCircle = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, contextW, contextH)];
    [color set];
    [bigCircle fill];
    
    // 2. 设置裁剪区域(小圆)
    UIBezierPath *clipPath = [UIBezierPath bezierPathWithOvalInRect:
                               CGRectMake(border, border, image.size.width, image.size.height)];
    [clipPath addClip]; // 后续绘制被限制在小圆内
    
    // 3. 绘制原图(被裁剪为圆形)
    [image drawAtPoint:CGPointMake(border, border)];
    
    // 4. 生成新图片
    UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return result;
}

@end

十、屏幕截图

方式一:renderInContext:(兼容旧系统)

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    
    UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 将 view 的 layer 渲染到上下文
    [self.view.layer renderInContext:ctx];
    
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    NSData *data = UIImagePNGRepresentation(image);
    NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"screenshot.png"];
    [data writeToFile:path atomically:YES];
}

方式二:drawViewHierarchyInRect:afterScreenUpdates:(iOS 7+,推荐)

UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
[self.view drawViewHierarchyInRect:self.view.bounds afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

drawViewHierarchyInRect: 比 renderInContext: 性能更好,且能正确捕获 UIVisualEffectView 模糊等特效。iOS 7+ 推荐使用。


十一、区域截图(QQ 截图效果)

042155504863780

042154299703113

拖拽选择区域,裁剪出对应部分:

@interface ScreenshotViewController ()
@property (nonatomic, weak) UIView *cover;     // 半透明蒙板
@property (nonatomic, assign) CGPoint startPoint; // 起始点
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end

@implementation ScreenshotViewController

- (UIView *)cover {
    if (!_cover) {
        UIView *view = [[UIView alloc] init];
        view.backgroundColor = [UIColor blackColor];
        view.alpha = 0.5;
        [self.view addSubview:view];
        _cover = view;
    }
    return _cover;
}

- (IBAction)pan:(UIPanGestureRecognizer *)sender {
    CGPoint curP = [sender locationInView:self.imageView];
    
    if (sender.state == UIGestureRecognizerStateBegan) {
        self.startPoint = curP;
    }
    
    // 更新蒙板 frame
    CGFloat w = curP.x - self.startPoint.x;
    CGFloat h = curP.y - self.startPoint.y;
    self.cover.frame = CGRectMake(self.startPoint.x, self.startPoint.y, w, h);
    
    if (sender.state == UIGestureRecognizerStateEnded) {
        // 裁剪选中区域
        UIGraphicsBeginImageContextWithOptions(self.imageView.bounds.size, NO, 0);
        
        UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.cover.frame];
        [path addClip]; // 裁剪区域
        
        [self.imageView.layer renderInContext:UIGraphicsGetCurrentContext()];
        
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        self.imageView.image = image;
        [self.cover removeFromSuperview];
    }
}

@end

十二、图片擦除

手指拖动时,擦除上层图片的对应区域,露出下层图片:

042157474237123

042200448301591

- (IBAction)pan:(UIPanGestureRecognizer *)sender {
    CGPoint curP = [sender locationInView:sender.view];
    
    // 擦除区域(以触摸点为中心的 30x30 正方形)
    CGFloat wh = 30;
    CGFloat x = curP.x - wh * 0.5;
    CGFloat y = curP.y - wh * 0.5;
    CGRect eraseRect = CGRectMake(x, y, wh, wh);
    
    UIGraphicsBeginImageContextWithOptions(sender.view.bounds.size, NO, 0);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 将当前图片渲染到上下文
    [sender.view.layer renderInContext:ctx];
    
    // 清除指定区域(透明)
    CGContextClearRect(ctx, eraseRect);
    
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    // 更新显示
    UIImageView *imageV = (UIImageView *)sender.view;
    imageV.image = image;
}

CGContextClearRect 会将指定区域清除为透明,需确保上下文 opaque=NO(不透明为 NO),否则清除区域为黑色。


十三、Swift 版本对照

画线

class DrawView: UIView {
    override func draw(_ rect: CGRect) {
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 50, y: 50))
        path.addLine(to: CGPoint(x: 200, y: 200))
        path.lineWidth = 2
        UIColor.red.setStroke()
        path.stroke()
    }
}

下载进度

class ProgressView: UIView {
    var progress: CGFloat = 0 {
        didSet { setNeedsDisplay() }
    }
    
    override func draw(_ rect: CGRect) {
        let center = CGPoint(x: bounds.width / 2, y: bounds.width / 2)
        let radius = bounds.width / 2 - 2
        let startA = -CGFloat.pi / 2
        let endA = startA + progress * 2 * CGFloat.pi
        
        let path = UIBezierPath(arcCenter: center, radius: radius,
                                 startAngle: startA, endAngle: endA, clockwise: true)
        path.lineWidth = 5
        UIColor.blue.setStroke()
        path.stroke()
    }
}
class SnowView: UIView {
    private var snowY: CGFloat = 0
    
    override func awakeFromNib() {
        super.awakeFromNib()
        let link = CADisplayLink(target: self, selector: #selector(setNeedsDisplay))
        link.add(to: .main, forMode: .common)
    }
    
    override func draw(_ rect: CGRect) {
        if let image = UIImage(named: "雪花") {
            image.draw(at: CGPoint(x: 0, y: snowY))
        }
        snowY += 10
        if snowY > rect.height { snowY = 0 }
    }
}

图片水印(UIGraphicsImageRenderer,iOS 10+)

func watermark(image: UIImage, text: String) -> UIImage {
    let renderer = UIGraphicsImageRenderer(size: image.size)
    return renderer.image { _ in
        image.draw(at: .zero)
        let attrs: [NSAttributedString.Key: Any] = [.foregroundColor: UIColor.red]
        text.draw(at: .zero, withAttributes: attrs)
    }
}

圆形裁剪带圆环

extension UIImage {
    static func circle(name: String, borderColor: UIColor, borderWidth: CGFloat) -> UIImage? {
        guard let image = UIImage(named: name) else { return nil }
        let contextW = image.size.width + 2 * borderWidth
        let contextH = image.size.height + 2 * borderWidth
        let size = CGSize(width: contextW, height: contextH)
        
        let renderer = UIGraphicsImageRenderer(size: size)
        return renderer.image { _ in
            // 大圆(圆环)
            let bigCircle = UIBezierPath(ovalIn: CGRect(origin: .zero, size: size))
            borderColor.setFill()
            bigCircle.fill()
            
            // 裁剪区域
            let clipRect = CGRect(x: borderWidth, y: borderWidth,
                                   width: image.size.width, height: image.size.height)
            UIBezierPath(ovalIn: clipRect).addClip()
            
            // 绘制原图
            image.draw(at: CGPoint(x: borderWidth, y: borderWidth))
        }
    }
}

屏幕截图

func screenshot(view: UIView) -> UIImage {
    let renderer = UIGraphicsImageRenderer(size: view.bounds.size)
    return renderer.image { _ in
        view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
    }
}

图片擦除

@IBAction func pan(_ sender: UIPanGestureRecognizer) {
    guard let view = sender.view else { return }
    let curP = sender.location(in: view)
    let wh: CGFloat = 30
    let eraseRect = CGRect(x: curP.x - wh/2, y: curP.y - wh/2, width: wh, height: wh)
    
    let renderer = UIGraphicsImageRenderer(size: view.bounds.size)
    let image = renderer.image { ctx in
        view.layer.render(in: ctx.cgContext)
        ctx.cgContext.clear(eraseRect)
    }
    
    if let imageView = view as? UIImageView {
        imageView.image = image
    }
}

十四、总结

  • 画线:UIBezierPath 拼接路径(moveToPoint/addLineToPoint/addQuadCurveToPoint/bezierPathWithArcCenter),通过 stroke/fill 渲染;不相连的线段需重新 moveToPoint。
  • 重绘机制:drawRect: 只调用一次,更新需通过 setNeedsDisplay 触发;不可手动调用 drawRect:。
  • 下载进度圆:起始角 -M_PI_2(12 点方向),结束角随进度变化,setter 中调用 setNeedsDisplay。
  • 饼图:遍历数据,每段扇形从 startA 到 endA,addLineToPoint:center 连接圆心,fill 自动关闭路径。
  • UIKit 绘图:NSString drawAtPoint:withAttributes: 绘制富文本;UIImage drawAtPoint:/drawInRect: 绘制图片;UIRectClip 设置裁剪区域。
  • CADisplayLink:与屏幕刷新同步(60fps),适合频繁重绘的动画;需添加到 NSRunLoopCommonModes 才能在 UIScrollView 滚动时继续执行。
  • 上下文栈:CGContextSaveGState/CGContextRestoreGState 成对使用,临时修改绘图状态。
  • 矩阵变换:CTM 支持 Translate/Rotate/Scale,累积生效,顺序敏感,配合上下文栈临时使用。
  • 图片水印:UIGraphicsBeginImageContextWithOptions 开启位图上下文,绘制原图+水印,UIGraphicsGetImageFromCurrentImageContext 生成新图。
  • 圆形裁剪:先画大圆(圆环),再设置小圆裁剪区域,最后绘制原图;addClip 限制后续绘制区域。
  • 屏幕截图:renderInContext:(旧)或 drawViewHierarchyInRect:afterScreenUpdates:(iOS 7+ 推荐,性能好、支持模糊特效)。
  • 区域截图:半透明蒙板指示选区,addClip 裁剪后渲染 layer。
  • 图片擦除:CGContextClearRect 清除指定区域为透明,上下文需 opaque=NO。
  • 现代 API:iOS 10+ 推荐 UIGraphicsImageRenderer,自动管理上下文生命周期,无需手动 Begin/End。

posted @ 2015-08-04 22:02  Mr.陳  阅读(1123)  评论(0)    收藏  举报