广告
返回顶部
首页 > 资讯 > 移动开发 >iOS坐标系的深入探究
  • 217
分享到

iOS坐标系的深入探究

ios坐标系转换 2022-05-15 12:05:51 217人浏览 八月长安
摘要

前言 app在渲染视图时,需要在坐标系中指定绘制区域。 这个概念看似乎简单,事实并非如此。 When an app draws something in iOS, it has

前言

app在渲染视图时,需要在坐标系中指定绘制区域。

这个概念看似乎简单,事实并非如此。

When an app draws something in iOS, it has to locate the drawn content in a two-dimensional space defined by a coordinate system. This notion might seem straightforward at first glance, but it isn't.

正文

我们先从一段最简单的代码入手,在drawRect中显示一个普通的UILabel;

为了方便判断,我把整个view的背景设置成黑色:


- (void)drawRect:(CGRect)rect {
 [super drawRect:rect];
 CGContextRef context = UIGraphicsGetCurrentContext();
 NSLog(@"CGContext default CTM matrix %@", NSStringFromCGAffineTransfORM(CGContextGetCTM(context)));
 UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 28)];
 testLabel.text = @"测试文本";
 testLabel.font = [UIFont systemFontOfSize:14];
 testLabel.textColor = [UIColor whiteColor];
 [testLabel.layer renderInContext:context];
}

这段代码首先创建一个UILabel,然后设置文本,显示到屏幕上,没有修改坐标。

所以按照UILabel.layer默认的坐标(0, 0),在左上角进行了绘制。

UILabel绘制

接着,我们尝试使用CoreText来渲染一段文本。


- (void)drawRect:(CGRect)rect {
 [super drawRect:rect];
 CGContextRef context = UIGraphicsGetCurrentContext();
 NSLog(@"CGContext default matrix %@", NSStringFromCGAffineTransform(CGContextGetCTM(context)));
 NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:@"测试文本" attributes:@{
             NSForegroundColorAttributeName:[UIColor whiteColor],
             NSFontAttributeName:[UIFont systemFontOfSize:14],
             }];
 CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attrStr); // 根据富文本创建排版类CTFramesetterRef
 UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 20)];
 CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), bezierPath.CGPath, NULL); // 创建排版数据
 CTFrameDraw(frameRef, context);
}

首先用NSString创建一个富文本,然后根据富文本创建CTFramesetterRef,结合CGRect生成的UIBezierPath,我们得到CTFrameRef,最终渲染到屏幕上。

但是结果与上文不一致:文字是上下颠倒。

CoreText的文本绘制

从这个不同的现象开始,我们来理解iOS的坐标系。

坐标系概念

在iOS中绘制图形必须在一个二维的坐标系中进行,但在iOS系统中存在多个坐标系,常需要处理一些坐标系的转换。 先介绍一个图形上下文(graphics context)的概念,比如说我们常用的CGContext就是Quartz 2D的上下文。图形上下文包含绘制所需的信息,比如颜色、线宽、字体等。用我们在windows常用的画图来参考,当我们使用画笔🖌在白板中写字时,图形上下文就是画笔的属性设置、白板大小、画笔位置等等。

iOS中,每个图形上下文都会有三种坐标:

绘制坐标系(也叫用户坐标系),我们平时绘制所用的坐标系;

视图(view)坐标系,固定左上角为原点(0,0)的view坐标系;

物理坐标系,物理屏幕中的坐标系,同样是固定左上角为原点;

根据我们绘制的目标不同(屏幕、位图、pdf等),会有多个context;

Quartz常见的绘制目标

不同context的绘制坐标系各不相同,比如说UIKit的坐标系为左上角原点的坐标系,CoreGraphics的坐标系为左下角为原点的坐标系;

CoreGraphics坐标系和UIKit坐标系的转换

CoreText基于CoreGraphics,所以坐标系也是CoreGraphics的坐标系。

我们回顾下上文提到的两个渲染结果,我们产生如下疑问:

UIGraphicsGetCurrentContext返回的是CGContext,代表着是左下角为原点的坐标系,用UILabel(UIKit坐标系)可以直接renderInContext,并且“测”字对应为UILabel的(0,0)位置,是在左上角?

当用CoreText渲染时,坐标是(0,0),但是渲染的结果是在左上角,并不是在左下角;并且文字是上下颠倒的。

为了探究这个问题,我在代码中加入了一行log:


NSLog(@"CGContext default matrix %@", NSStringFromCGAffineTransform(CGContextGetCTM(context)));

其结果是CGContext default matrix [2, 0, 0, -2, 0, 200];

CGContextGetCTM返回是CGAffineTransform仿射变换矩阵:

一个二维坐标系上的点p,可以表达为(x, y, 1),乘以变换的矩阵,如下:

把结果相乘,得到下面的关系

此时,我们再来看看打印的结果[2, 0, 0, -2, 0, 200],可以化简为

x' = 2x, y' = 200 - 2y

因为渲染的view高度为100,所以这个坐标转换相当于把原点在左下角(0,100)的坐标系,转换为原点在左上角(0,0)的坐标系!通常我们都会使用UIKit进行渲染,所以iOS系统在drawRect返回CGContext的时候,默认帮我们进行了一次变换,以方便开发者直接用UIKit坐标系进行渲染。

我们尝试对系统添加的坐标变换进行还原:

先进行CGContextTranslateCTM(context, 0, self.bounds.size.height);

对于x' = 2x, y' = 200 - 2y,我们使得x=x,y=y+100;(self.bounds.size.height=100

于是有x' = 2x, y' = 200-2(y+100) = -2y;

再进行CGContextScaleCTM(context, 1.0, -1.0);

对于x' = 2x, y' = -2y,我们使得x=x, y=-y;

于是有 x'=2x, y' = -2(-y) = 2y;


- (void)drawRect:(CGRect)rect {
 [super drawRect:rect];
 CGContextRef context = UIGraphicsGetCurrentContext();
 CGContextTranslateCTM(context, 0, self.bounds.size.height);
 CGContextScaleCTM(context, 1.0, -1.0);
 NSLog(@"CGContext default matrix %@", NSStringFromCGAffineTransform(CGContextGetCTM(context)));
 NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:@"测试文本" attributes:@{
             NSForegroundColorAttributeName:[UIColor whiteColor],
             NSFontAttributeName:[UIFont systemFontOfSize:14],
             }];
 CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attrStr); // 根据富文本创建排版类CTFramesetterRef
 UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 20)];
 CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), bezierPath.CGPath, NULL); // 创建排版数据
 CTFrameDraw(frameRef, context);
}

通过log也可以看出来CGContext default matrix [2, 0, -0, 2, 0, 0];

最终结果如下,文本从左下角开始渲染,并且没有出现上下颠倒的情况。

这时我们产生新的困扰:

用CoreText渲染文字的上下颠倒现象解决,但是修改后的坐标系UIKit无法正常使用,如何兼容两种坐标系?

iOS可以使用CGContextSaveGState()方法暂存context状态,然后在CoreText绘制完后通过CGContextRestoreGState ()可以恢复context的变换。


- (void)drawRect:(CGRect)rect {
 [super drawRect:rect];

 CGContextRef context = UIGraphicsGetCurrentContext();
 NSLog(@"CGContext default matrix %@", NSStringFromCGAffineTransform(CGContextGetCTM(context)));
 CGContextSaveGState(context);
 CGContextTranslateCTM(context, 0, self.bounds.size.height);
 CGContextScaleCTM(context, 1.0, -1.0);
 NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:@"测试文本" attributes:@{
                         NSForegroundColorAttributeName:[UIColor whiteColor],
                         NSFontAttributeName:[UIFont systemFontOfSize:14],
                         }];
 CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attrStr); // 根据富文本创建排版类CTFramesetterRef
 UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 20)];
 CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), bezierPath.CGPath, NULL); // 创建排版数据
 CTFrameDraw(frameRef, context);
 CGContextRestoreGState(context);
 
 
 NSLog(@"CGContext default CTM matrix %@", NSStringFromCGAffineTransform(CGContextGetCTM(context)));
 UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 20)];
 testLabel.text = @"测试文本";
 testLabel.font = [UIFont systemFontOfSize:14];
 testLabel.textColor = [UIColor whiteColor];
 [testLabel.layer renderInContext:context];
}

渲染结果如下,控制台输出的两个matrix都是[2, 0, 0, -2, 0, 200];

遇到的问题

1、UILabel.layer在renderInContext的时候frame失效

初始化UILabel时设定了frame,但是没有生效。


UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 100, 28)];

这是因为frame是在上一层view中坐标的偏移,在renderInContext中坐标起点与frame无关,所以需要修改的是bounds属性:


testLabel.layer.bounds = CGRectMake(50, 50, 100, 28);

2、renderInContext和drawInContext的选择

在把UILabel.layer渲染到context的时候,应该采用drawInContext还是renderInContext?

虽然这两个方法都可以生效,但是根据画线部分的内容来判断,还是采用了renderInContext,并且问题1就是由这里的一句Renders in the coordinate space of the layer,定位到问题所在。

3、如何理解CoreGraphics坐标系不一致后,会出现绘制结果异常?

我的理解方法是,我们可以先不考虑坐标系变换的情况。

如下图,上半部分是普通的渲染结果,可以很容易的想象;

接下来是增加坐标变换后,坐标系变成原点在左上角的顶点,相当于按照下图的虚线进行了一次垂直的翻转。

也可以按照坐标系变换的方式去理解,将左下角原点的坐标系相对y轴做一次垂直翻转,然后向上平移height的高度,这样得到左上角原点的坐标系。

附录

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对编程网的支持。

--结束END--

本文标题: iOS坐标系的深入探究

本文链接: https://www.lsjlt.com/news/30188.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • iOS坐标系的深入探究
    前言 app在渲染视图时,需要在坐标系中指定绘制区域。 这个概念看似乎简单,事实并非如此。 When an app draws something in iOS, it has...
    99+
    2022-05-15
    ios 坐标系 转换
  • 深入浅析Android坐标系统
     1 背景 去年有很多人私信告诉我让说说自定义控件,其实通观网络上的很多博客都在讲各种自定义控件,但是大多数都是授之以鱼,却很少有较为系统性授之于渔的文章,同时由于自...
    99+
    2022-06-06
    系统 Android
  • 深入探究Golang中flag标准库的使用
    目录1.使用1.1示例1.2标志类型1.3标志语法2.源码解读2.1定义标志2.2解析标志参数2.3其他代码3.总结在使用 Go 进行开发的过程中,命令行参数解析是我们经常遇到的需求...
    99+
    2023-05-18
    Golang flag标准库使用 Golang flag标准库 Golang flag
  • 深入探究Golang中log标准库的使用
    目录使用源码使用建议Go 语言标准库中的 log 包设计简洁明了,易于上手,可以轻松记录程序运行时的信息、调试错误以及跟踪代码执行过程中的问题等。使用 log 包无需繁琐的配置即可直...
    99+
    2023-05-19
    Golang log标准库用法 Golang log标准库使用 Golang log标准库 Golang log
  • Golang中map的深入探究
    目录简介Map 的底层内存模型Map 的存与取底层代码Map 的扩容第一种情况第二种情况Map 的有序性Map 的并发总结简介 本文主要通过探究在golang 中map的数据结构及源...
    99+
    2022-11-11
  • C++深入探究不同的继承体系
    目录单继承多继承菱形继承概念存在的问题解决方案菱形虚拟继承虚拟继承什么是虚拟继承内存层面理解虚拟继承虚拟继承和普通单继承的区别虚拟继承+菱形继承声明: 本文的测试环境为Windows...
    99+
    2022-11-13
  • Android 中的注解深入探究
    本文系GDG Android Meetup分享内容总结文章 注解是我们经常接触的技术,Java有注解,Android也有注解,本文将试图介绍Android中的注解,以及Butt...
    99+
    2022-06-06
    注解 Android
  • Java深入探究Object类的方法
    目录1.equals方法1.API中equals方法的介绍2.==和equals 的对比2.hashCode方法3.toString方法4.finalize方法本文主要带大家看看Ob...
    99+
    2022-11-13
  • C++深入探究引用的使用
    目录一. 引用的概念二. 引用特性三. 常引用四. 使用场景1. 做参数2. 做返回值3. 做返回值需要注意的问题五. 传值传引用效率对比1. 值和引用传参时的效率比较2. 值和引用...
    99+
    2022-11-13
  • MySQL中join查询的深入探究
    目录前引索引对 join 查询的影响数据准备有索引查询过程无索引查询过程了解 Block Nested-Loop JoinBlock Nested-Loop Join查询过程Join...
    99+
    2022-11-13
    mysql join查询 流程 mysql join方式 mysql join查询
  • SQL深入探究存储的过程
    目录存储过程简介存储过程的创建及调用存储过程的删除存储过程的优缺点现需要向学生表中插入新的学生数据。但在插入学生数据的时,需要同 时检查老师表里的数据。如果插入学生的老师不在老师表里,则先向老师表中插入一条老师数据,再向...
    99+
    2023-01-05
    SQL存储过程的作用 SQL存储过程
  • C++深入探究list的模拟实现
    目录迭代器正向迭代器类反向迭代器类push_back尾插函数push_front头插函数insert插入函数erase删除函数pop_front函数pop_back函数构造函数析构函...
    99+
    2022-11-13
  • C语言深入探究栈的原理
    栈 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。 出栈:栈的删除操作叫做出栈。出数据也在栈顶。 栈的实现 栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优...
    99+
    2022-11-12
  • 关于Redis持久化的深入探究
    目录Redis持久化1、为什么需要持久化2、RDB(Redis Database)2.1 官网介绍2.2 什么是RDB2.3 操作步骤3、AOF(Append Only File)3.1 什么是AOF?3.2 AOF持久...
    99+
    2023-05-22
    Redis持久化详解 Redis 持久化
  • 深入探究Java中的类加载机制
    目录前言步入正题类的加载过程:1.加载2.验证3.准备4.解析5.初始化类加载器源码总结前言 学生时代应抱着问题去学习一门语言,例如:在学习java语言的过程中,我遇到过java主方...
    99+
    2022-11-12
  • 深入浅出探究JavaScript中的async与await
    目录1、前言2、详解2.1、async2.1.1、函数返回非Promise对象2.1.2、函数返回Promise对象2.2、await2.3、async、await结合使用2.4、a...
    99+
    2022-11-12
  • Java深入探究关键字abstract的使用
    目录1. 理解2. 作用3. 修饰类-抽象类4. 修饰方法-抽象方法5. 代码演示6. 经典题目7. 抽象类的匿名子类8. 应用-模板方法设计模式(TemplateMethod)1....
    99+
    2022-11-13
  • 深入探究Java原型模式的魅力
    目录1. 什么是Java原型模式?2. 为什么要使用Java原型模式?3. Java原型模式的实现方式3.1浅克隆3.2 深克隆4. Java原型模式的优点5. Java原型模式的缺...
    99+
    2023-05-20
    Java原型设计模式 Java原型模式
  • 深入探究C语言中的二叉树
    目录1.树概念及结构1.1树的概念 1.2 树的相关概念1.3 树的表示2.二叉树概念及结构   2.1概念2.2 特殊的二叉树2.3 二叉树的性质&n...
    99+
    2023-05-19
    C语言二叉树 C语言数据结构
  • C语言深入探究函数的溯源
    目录一、函数的由来二、模块化程序设计三、C 语言中的模块化四、面向过程的程序设计五、声名和定义六、小结一、函数的由来 二、模块化程序设计 三、C 语言中的模块化 四、面向过程的...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作