《iOS Drawing Practical UIKit Solutions》读书笔记(一)——Drawing Context

Frameworks

iOS 绘图基于两个库:
1. UIKit
2. Quartz 2D

UIKit我们很熟悉,常用的控件UIView,UIButton以及各种ViewController,均来自于UIKit库。

Quartz 2D则是基于C语音的绘图库,我们应该听说过,但是真正应用的并不多。Quartz 2D的底层实现是基于Core Graphics的。因此Quartz的一些数据类型,均以‘CG’开头,如CGRect,CGColorRef等。

Quartz不仅仅提供了绘图功能,同时,其还提供了动画,图片编辑,PDF文件创建、编辑等功能。(在Quartz的内部图像模型,其实与PDF文件模型的定义很类似)

Drawing Context

无论我们使用UIKit还是Quartz库,所有的操作均是作用于Context上的。

Context的作用类似于油画中的画布,我们可以设置画布的属性,Pop/Push画布,以及获取当前画布等。

When to Draw?

话说回来,UIKit库为我们封装了大量的绘图控件,如UIButton,我们可以仅用几行代码就创建出一个能够响应事件的图片文字按钮。那么,什么时候需要我们大费周章的去draw UI呢?

如果符合下面4种情况之一,你可以考虑去draw:

  • Creating custom views
  • building images
  • create PDFs
  • building with Core Graphics

Creating Custom Views

一个UIView就像一个空白的画布,当我们需要自定义UIView的内容时,可以重写

- (void)drawRect:(CGRect)rect

方法。

与之匹配的,专门用于绘制打印内容的方法:

- (void)drawRect:(CGRect)rect forViewPrintFormatter:(UIViewPrintFormatter *)formatter

Building Images

利用UIKit,你可以在UIKit image context中,生成一个UIImage或者对其进行修改。

虽然生成(或修改)UIImage会使用运算能力,但是能够使我们的APP体积更小,显示更为灵活。

Creating PDFs

类似于Image,你可以在UIKit PDF context中,生成,编辑一个PDF文件。

Build with Core Graphics

当UIKit的功能不能够满足我们时,就需要转向Quartz了。我们可以在Core Graphic 的context中进行一些基于Core Graphics操作。

Contexts

通过上面我们已经知道,iOS中要进行任何的绘图操作,均要基于Context。

Context记录了当前绘画的所有信息——画布是否翻转,绘图的颜色,亮度等。

在iOS中,我们大致会使用如下几种Context:

  • bitmap contexts
  • PDF contexts
  • image contexts

Bitmap Contexts

Bitmap contexts 实际上是一个矩形data数组,数组的大小取决于图片的颜色类型。如普通的图片,一个像素可能会占用4bytes,分别表示RGB值以及透明度。而不透明图片可能仅有RGB值,而忽略透明度以节省空间。对于黑白图片,则仅会用1到2个bytes。

在UIView的方法

- (void)drawRect:(CGRect)rect

中,系统会自动创建一个Context,来接受绘图。利用UIGraphicsGetCurrentContext()查看Context,实际上其类型就是Bitmap Context

这里写图片描述

PDF Contexts

PDF contexts 和 Bitmap context的操作是一模一样的。但是还是有些许的差别,PDF contexts的数据信息包括矢量数据,这可以使PDF文件在不同的解析度下不会发生形变。同时PDF context还是有page的概念,一个PDF文件包括若干Pages。

Core Image Contexts

Core Image Contexts 是用来讲Core Image 对象转换为Quartz 2D和OpenGL。它会利用GPU加速,Core Image的对象类型为CIColor何CIImage.

在UIKit中建立Contexts

在UIKit中创建一个Contexts很简单,仅需执行如下代码

 UIGraphicsBeginImageContext(CGSizeMake(100, 100));
 NSLog(@"IN Context is %@", UIGraphicsGetCurrentContext());
 UIGraphicsEndImageContext();

为了深入理解这一对函数,我们做如下实验:

NSLog(@"OUT 1 Context is %@", UIGraphicsGetCurrentContext());
UIGraphicsBeginImageContext(CGSizeMake(100, 100));
NSLog(@"IN Context is %@", UIGraphicsGetCurrentContext());
UIGraphicsEndImageContext();
NSLog(@"OUT 2 Context is %@", UIGraphicsGetCurrentContext());

输出为:
这里写图片描述

可见,UIKit创建的Contexts生命周期仅存在于BeginContext于EndContext之间。

device scale

UIKit创建Image Contexts还有另一个更详细的接口,

 UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale)

这里可以指定更详细的Contexts。

Creating PDF Context

当需要创建PDF文件时,我们可以创建一个PDF context,其类似Bitmap context:

UIGraphicsBeginPDFContextToFile(NSString * _Nonnull path, CGRect bounds, NSDictionary * _Nullable documentInfo);
UIGraphicsBeginPDFPage();
// Draw PDF here
UIGraphicsEndPDFContext();

创建一个PDF文件,你必须将它draw到一个文件或Data对象中。因此,BeginPDFContext有两个版本:

  UIGraphicsBeginPDFContextToFile(NSString * _Nonnull path, CGRect bounds, NSDictionary * _Nullable documentInfo);

    UIGraphicsBeginPDFContextToData(NSMutableData * _Nonnull data, CGRect bounds, NSDictionary * _Nullable documentInfo)

同时,我们需要创建PDF的‘页’,用方法:

UIGraphicsBeginPDFPage();

这里,我们不需要endPDFPage

在Quartz中创建Contexts

在Quartz中创建Contexts,相较于UIKit,更为复杂。主要体现在:

  1. 时刻需要注意手动Release Quartz创建的C类型对象
  2. Quartz的C语言接口更为复杂

    在Quartz中创建Context:

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    if (colorSpace == NULL) {
        NSLog(@"Error allocating color space");
        return nil;
    }
#define BITS_PER_COMPONENT  8
#define ARGB_COUNT 4
    // Create bitmap context
    CGContextRef context = CGBitmapContextCreate(NULL, width, height,
                                                 BITS_PER_COMPONENT,
                                                 width * ARGB_COUNT,
                                                 colorSpace,
                                                 (CGBitmapInfo)kCGImageAlphaPremultipliedFirst);
    if (context == NULL) {
        // 注意这里要释放之前创建的CG对象
        CGColorSpaceRelease(colorSpace);
        return nil;
    }

    CGImageRef imageRef = CGBitmapContextCreateImage(context);
    UIImage *image = [UIImage imageWithCGImage:imageRef];
    // 释放CG对象
    CGColorSpaceRelease(colorSpace);
    CGContextRelease(context);
    CFRelease(imageRef);

Drawing into Contexts

大部分的Quartz方法都需要一个Context作为参数。
获取Quartz Context有两个方法:

  1. 如上面所示,用CGCreateContext方法创建一个Context
  2. 有UIKit的Context转换为Quartz Context

    下面是一个使用Quartz Context的例子

 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    if (colorSpace == NULL) {
        NSLog(@"Error allocating color space");
        return;
    }
#define BITS_PER_COMPONENT  8
#define ARGB_COUNT 4
    // Create bitmap context
    CGContextRef context = CGBitmapContextCreate(NULL, 100, 200,
                                                 BITS_PER_COMPONENT,
                                                 100 * ARGB_COUNT,
                                                 colorSpace,
                                                 (CGBitmapInfo)kCGImageAlphaPremultipliedFirst);
    if (context == NULL) {
        CGColorSpaceRelease(colorSpace);
        return;
    }

    CGContextSetLineWidth(context, 4);
    CGContextSetStrokeColorWithColor(context, [UIColor grayColor].CGColor);
    CGContextStrokeEllipseInRect(context, CGRectMake(0, 0, 100, 200));
    CGImageRef imageRef = CGBitmapContextCreateImage(context);
    UIImage *image = [UIImage imageWithCGImage:imageRef];

    CGColorSpaceRelease(colorSpace);
    CGContextRelease(context);
    CFRelease(imageRef);

将UIKit Context转换为Quartz Context

相较于Quartz,我们运用UIKit能够更简便地运用UIKit所创建的Context。
而我们可以使用函数

CGContextRef __nullable UIGraphicsGetCurrentContext(void)

将UIKit context转换为Quartz contest。

例如对于上面一段画椭圆的代码,我们可以用UIKit简写为:

     UIGraphicsBeginImageContextWithOptions(CGSize size,
                                           BOOL opaque,
                                           CGFloat scale);
    // Retrieve the current context
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextSetLineWidth(context, 4);
    CGContextSetStrokeColorWithColor(context, [UIColor grayColor].CGColor);
    CGContextStrokeEllipseInRect(context, CGRectMake(0, 0, 100, 200));
    CGImageRef imageRef = CGBitmapContextCreateImage(context);
    UIImage *image = [UIImage imageWithCGImage:imageRef];

    UIGraphicsEndImageContext();

Push/Pop Context

我们在使用UIKit Context时,会成对的使用

UIGraphicsBegin…Context

// operation with Context

UIGraphicsEnd…Context

其中会创建Context,而创建的Context的生命周期会在EndContext结束。

但是,我们可以通过Push/Pop Context的方式,来保留Context,使其在End…Context之外,仍能够使用。

如下代码:

    NSLog(@"Context0 is %@", UIGraphicsGetCurrentContext());
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100),
                                           YES,
                                           0);
    // Retrieve the current context
    CGContextRef context = UIGraphicsGetCurrentContext();
    NSLog(@"Context1 is %@", UIGraphicsGetCurrentContext());
    UIGraphicsPushContext(context);
    NSLog(@"Context2 is %@", UIGraphicsGetCurrentContext());

    UIGraphicsEndImageContext();
    NSLog(@"Context3 is %@", UIGraphicsGetCurrentContext());
    UIGraphicsPopContext();
    NSLog(@"Context4 is %@", UIGraphicsGetCurrentContext());
    UIGraphicsPopContext();
    NSLog(@"Context5 is %@", UIGraphicsGetCurrentContext());

输出为:

这里写图片描述

PS:

在UIView的方法drawRect之前,系统会自动为我们Push一个context 到 UIKit的Context Stack中。因此,在drawRect方法中,我们不需要显示的创建Context。

UIKit Current Context

基本上说有的Quartz 函数都接受context参数,而UIKit版本的方法却不用。因为UIKit会自动维护一个graphics Stack,UIKit的方法会自动使用处于stack top的Context。

如果要混合使用Core Graphics与UIKit,则需按照如下流程:

  1. Create a Core Graphics context
  2. Push the context with UIKit method UIGraphicsPushContext()
  3. Use UIKit method
  4. Use Core Craphics method with context parameters
  5. Pop the context with UIGraphicsPopContext()
  6. Release the cotontext’s momery

UIKit and Quartz Colors

UIKit的对象是支持ARC的,而Quartz对象则是C语言形式,需要人工的retain 与 release。基本每一个UIKit对象都有对应的Quartz对象。

通常,UIKit与Quartz对象的转换是toll free的,仅需要关键字__bridge

而有些对象,是不可以直接用__bridge的。幸运的是,我们可以通过属性来获取对应的对象。如UIKit与Quartz中Colors对象的转换,则应该是

UIColor *red = [UIColor redColor];
CGColorRef redRef = red.CGColor;

这里要注意redRef对象的保持,不要让red由于ARC自动释放掉。

The Painter’s Model

iOS默认用一种被称为‘Painter’s Model’来绘制内容。形象的说,就像是画油画一样,后绘制的内容,会覆盖在之前绘制的内容之上。

这里写图片描述

如图,先绘制了紫色的圆形,在绘制了绿色圆形,绿色圆形会覆盖在紫色圆形之上。

Context State

我们在绘制图形时,可以指定图形的边框颜色与填充色等信息。

[greenColor setFill];
[purpleColor setStroke];
[bunnyPath fill];
[bunnyPath stroke];

我们将这些绘图的状态,称为graphic state

Applying State

每一个Context均有一个graphic state的stack。当我们创建新的context的时候,其graphic state stack是新的。

我们可以通过Push/Pop来操作graphic state stack(GState)。

GState stack与UIKit所管理的Context stack有所不同。
UIKit管理的context stack是针对多个context之间切换而言的。而GState stack则是对于某一个context来说的。

我们可以使用

CGContextSaveGState();
CGContextRestoreGState();

来push,pop所存储的GState。

Context的state包括如下内容:
这里写图片描述
这里写图片描述

Context 坐标系统

在UIKit中,坐标原点是以左上角开始的,而在Quartz中,则是以左下角开始的。
这里写图片描述

坐标系统取决于Context是如何被创建的,如果由UIKit函数创建,则符合UIKit坐标系,反之,符合Quartz坐标系。

我们可以通过垂直翻转Quartz context的坐标系,使得Quartz的Context 坐标系和UIKit的Context坐标系相同。代码如下:

- (void)FlipContextVertically:(CGSize) size{
    CGContextRef context = UIGraphicsGetCurrentContext();
    if (context == NULL) {
        NSLog(@"Error:NO context to flip");
        return;
    }
    CGAffineTransform transform = CGAffineTransformIdentity;
    transform = CGAffineTransformScale(transform, 1.0f, -1.0f);
    transform = CGAffineTransformTranslate(transform, 1.0f, -size.height);
    CGContextConcatCTM(context, transform);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
iOS 14官方UIKit是苹果公司针对iOS操作系统推出的一套官方用户界面工具包。它提供了许多用于构建iOS应用程序的组件和工具,使开发者可以快速方便地创建和设计用户界面。 iOS 14官方UIKit具有许多重要的功能和特性。首先,它提供了一套丰富的用户界面元素,例如按钮、标签、文本框等,开发者可以根据自己的需求进行定制和布局。这些元素经过精心设计,符合iOS最新的外观和交互规范,可以帮助开发者构建出现代和美观的应用程序界面。 其次,iOS 14官方UIKit还提供了许多动画和过渡效果,使得应用程序界面更加生动和流畅。开发者可以使用这些特效来增加用户体验,提高应用程序的交互性和吸引力。 第三,iOS 14官方UIKit还包括了一套强大的布局系统,使开发者能够轻松地管理界面元素的位置和大小。开发者可以使用自动布局和约束来确保应用程序在不同设备和屏幕尺寸上的适配性,从而保证用户在不同设备上都能够享受一致的用户体验。 此外,iOS 14官方UIKit还提供了许多其他功能,例如表格视图、集合视图、滚动视图等,使开发者能够更加便捷地展示和处理数据。还提供了多种手势识别功能,开发者可以通过手势来实现用户与应用程序的交互。 总而言之,iOS 14官方UIKit是一套强大而灵活的界面工具包,提供了丰富的用户界面组件和功能,帮助开发者快速构建出现代化和吸引人的iOS应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值