前言
成都創(chuàng)新互聯(lián)是一家企業(yè)級(jí)云計(jì)算解決方案提供商,超15年IDC數(shù)據(jù)中心運(yùn)營(yíng)經(jīng)驗(yàn)。主營(yíng)GPU顯卡服務(wù)器,站群服務(wù)器,多線服務(wù)器托管,海外高防服務(wù)器,大帶寬服務(wù)器,動(dòng)態(tài)撥號(hào)VPS,海外云手機(jī),海外云服務(wù)器,海外服務(wù)器租用托管等。
app在渲染視圖時(shí),需要在坐標(biāo)系中指定繪制區(qū)域。
這個(gè)概念看似乎簡(jiǎn)單,事實(shí)并非如此。
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.
正文
我們先從一段最簡(jiǎn)單的代碼入手,在drawRect中顯示一個(gè)普通的UILabel;
為了方便判斷,我把整個(gè)view的背景設(shè)置成黑色:
- (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 = @"測(cè)試文本";
testLabel.font = [UIFont systemFontOfSize:14];
testLabel.textColor = [UIColor whiteColor];
[testLabel.layer renderInContext:context];
}這段代碼首先創(chuàng)建一個(gè)UILabel,然后設(shè)置文本,顯示到屏幕上,沒(méi)有修改坐標(biāo)。
所以按照UILabel.layer默認(rèn)的坐標(biāo)(0, 0),在左上角進(jìn)行了繪制。

UILabel繪制
接著,我們嘗試使用CoreText來(lái)渲染一段文本。
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
NSLog(@"CGContext default matrix %@", NSStringFromCGAffineTransform(CGContextGetCTM(context)));
NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:@"測(cè)試文本" attributes:@{
NSForegroundColorAttributeName:[UIColor whiteColor],
NSFontAttributeName:[UIFont systemFontOfSize:14],
}];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attrStr); // 根據(jù)富文本創(chuàng)建排版類CTFramesetterRef
UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 20)];
CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), bezierPath.CGPath, NULL); // 創(chuàng)建排版數(shù)據(jù)
CTFrameDraw(frameRef, context);
}首先用NSString創(chuàng)建一個(gè)富文本,然后根據(jù)富文本創(chuàng)建CTFramesetterRef,結(jié)合CGRect生成的UIBezierPath,我們得到CTFrameRef,最終渲染到屏幕上。
但是結(jié)果與上文不一致:文字是上下顛倒。

CoreText的文本繪制
從這個(gè)不同的現(xiàn)象開(kāi)始,我們來(lái)理解iOS的坐標(biāo)系。
坐標(biāo)系概念
在iOS中繪制圖形必須在一個(gè)二維的坐標(biāo)系中進(jìn)行,但在iOS系統(tǒng)中存在多個(gè)坐標(biāo)系,常需要處理一些坐標(biāo)系的轉(zhuǎn)換。
先介紹一個(gè)圖形上下文(graphics context)的概念,比如說(shuō)我們常用的CGContext就是Quartz 2D的上下文。圖形上下文包含繪制所需的信息,比如顏色、線寬、字體等。用我們?cè)赪indows常用的畫圖來(lái)參考,當(dāng)我們使用畫筆🖌在白板中寫字時(shí),圖形上下文就是畫筆的屬性設(shè)置、白板大小、畫筆位置等等。
iOS中,每個(gè)圖形上下文都會(huì)有三種坐標(biāo):
1、繪制坐標(biāo)系(也叫用戶坐標(biāo)系),我們平時(shí)繪制所用的坐標(biāo)系;
2、視圖(view)坐標(biāo)系,固定左上角為原點(diǎn)(0,0)的view坐標(biāo)系;
3、物理坐標(biāo)系,物理屏幕中的坐標(biāo)系,同樣是固定左上角為原點(diǎn);

根據(jù)我們繪制的目標(biāo)不同(屏幕、位圖、PDF等),會(huì)有多個(gè)context;

Quartz常見(jiàn)的繪制目標(biāo)
不同context的繪制坐標(biāo)系各不相同,比如說(shuō)UIKit的坐標(biāo)系為左上角原點(diǎn)的坐標(biāo)系,CoreGraphics的坐標(biāo)系為左下角為原點(diǎn)的坐標(biāo)系;

CoreGraphics坐標(biāo)系和UIKit坐標(biāo)系的轉(zhuǎn)換
CoreText基于CoreGraphics,所以坐標(biāo)系也是CoreGraphics的坐標(biāo)系。
我們回顧下上文提到的兩個(gè)渲染結(jié)果,我們產(chǎn)生如下疑問(wèn):
UIGraphicsGetCurrentContext返回的是CGContext,代表著是左下角為原點(diǎn)的坐標(biāo)系,用UILabel(UIKit坐標(biāo)系)可以直接renderInContext,并且“測(cè)”字對(duì)應(yīng)為UILabel的(0,0)位置,是在左上角?
當(dāng)用CoreText渲染時(shí),坐標(biāo)是(0,0),但是渲染的結(jié)果是在左上角,并不是在左下角;并且文字是上下顛倒的。
為了探究這個(gè)問(wèn)題,我在代碼中加入了一行l(wèi)og:
NSLog(@"CGContext default matrix %@", NSStringFromCGAffineTransform(CGContextGetCTM(context)));
其結(jié)果是CGContext default matrix [2, 0, 0, -2, 0, 200];
CGContextGetCTM返回是CGAffineTransform仿射變換矩陣:

一個(gè)二維坐標(biāo)系上的點(diǎn)p,可以表達(dá)為(x, y, 1),乘以變換的矩陣,如下:

把結(jié)果相乘,得到下面的關(guān)系

此時(shí),我們?cè)賮?lái)看看打印的結(jié)果[2, 0, 0, -2, 0, 200],可以化簡(jiǎn)為
x' = 2x, y' = 200 - 2y
因?yàn)殇秩镜膙iew高度為100,所以這個(gè)坐標(biāo)轉(zhuǎn)換相當(dāng)于把原點(diǎn)在左下角(0,100)的坐標(biāo)系,轉(zhuǎn)換為原點(diǎn)在左上角(0,0)的坐標(biāo)系!通常我們都會(huì)使用UIKit進(jìn)行渲染,所以iOS系統(tǒng)在drawRect返回CGContext的時(shí)候,默認(rèn)幫我們進(jìn)行了一次變換,以方便開(kāi)發(fā)者直接用UIKit坐標(biāo)系進(jìn)行渲染。

我們嘗試對(duì)系統(tǒng)添加的坐標(biāo)變換進(jìn)行還原:
先進(jìn)行CGContextTranslateCTM(context, 0, self.bounds.size.height);
對(duì)于x' = 2x, y' = 200 - 2y,我們使得x=x,y=y+100;(self.bounds.size.height=100)
于是有x' = 2x, y' = 200-2(y+100) = -2y;
再進(jìn)行CGContextScaleCTM(context, 1.0, -1.0);
對(duì)于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:@"測(cè)試文本" attributes:@{
NSForegroundColorAttributeName:[UIColor whiteColor],
NSFontAttributeName:[UIFont systemFontOfSize:14],
}];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attrStr); // 根據(jù)富文本創(chuàng)建排版類CTFramesetterRef
UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 20)];
CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), bezierPath.CGPath, NULL); // 創(chuàng)建排版數(shù)據(jù)
CTFrameDraw(frameRef, context);
}通過(guò)log也可以看出來(lái)CGContext default matrix [2, 0, -0, 2, 0, 0];
最終結(jié)果如下,文本從左下角開(kāi)始渲染,并且沒(méi)有出現(xiàn)上下顛倒的情況。

這時(shí)我們產(chǎn)生新的困擾:
用CoreText渲染文字的上下顛倒現(xiàn)象解決,但是修改后的坐標(biāo)系UIKit無(wú)法正常使用,如何兼容兩種坐標(biāo)系?
iOS可以使用CGContextSaveGState()方法暫存context狀態(tài),然后在CoreText繪制完后通過(guò)CGContextRestoreGState ()可以恢復(fù)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:@"測(cè)試文本" attributes:@{
NSForegroundColorAttributeName:[UIColor whiteColor],
NSFontAttributeName:[UIFont systemFontOfSize:14],
}];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attrStr); // 根據(jù)富文本創(chuàng)建排版類CTFramesetterRef
UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 20)];
CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), bezierPath.CGPath, NULL); // 創(chuàng)建排版數(shù)據(jù)
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 = @"測(cè)試文本";
testLabel.font = [UIFont systemFontOfSize:14];
testLabel.textColor = [UIColor whiteColor];
[testLabel.layer renderInContext:context];
}渲染結(jié)果如下,控制臺(tái)輸出的兩個(gè)matrix都是[2, 0, 0, -2, 0, 200];
遇到的問(wèn)題
1、UILabel.layer在renderInContext的時(shí)候frame失效
初始化UILabel時(shí)設(shè)定了frame,但是沒(méi)有生效。
UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 100, 28)];
這是因?yàn)閒rame是在上一層view中坐標(biāo)的偏移,在renderInContext中坐標(biāo)起點(diǎn)與frame無(wú)關(guān),所以需要修改的是bounds屬性:
testLabel.layer.bounds = CGRectMake(50, 50, 100, 28);
2、renderInContext和drawInContext的選擇
在把UILabel.layer渲染到context的時(shí)候,應(yīng)該采用drawInContext還是renderInContext?

雖然這兩個(gè)方法都可以生效,但是根據(jù)畫線部分的內(nèi)容來(lái)判斷,還是采用了renderInContext,并且問(wèn)題1就是由這里的一句Renders in the coordinate space of the layer,定位到問(wèn)題所在。
3、如何理解CoreGraphics坐標(biāo)系不一致后,會(huì)出現(xiàn)繪制結(jié)果異常?
我的理解方法是,我們可以先不考慮坐標(biāo)系變換的情況。
如下圖,上半部分是普通的渲染結(jié)果,可以很容易的想象;
接下來(lái)是增加坐標(biāo)變換后,坐標(biāo)系變成原點(diǎn)在左上角的頂點(diǎn),相當(dāng)于按照下圖的虛線進(jìn)行了一次垂直的翻轉(zhuǎn)。

也可以按照坐標(biāo)系變換的方式去理解,將左下角原點(diǎn)的坐標(biāo)系相對(duì)y軸做一次垂直翻轉(zhuǎn),然后向上平移height的高度,這樣得到左上角原點(diǎn)的坐標(biāo)系。
附錄
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)創(chuàng)新互聯(lián)的支持。
新聞標(biāo)題:iOS坐標(biāo)系的深入探究
文章起源:http://chinadenli.net/article28/jpspjp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供營(yíng)銷型網(wǎng)站建設(shè)、App開(kāi)發(fā)、服務(wù)器托管、關(guān)鍵詞優(yōu)化、Google、網(wǎng)頁(yè)設(shè)計(jì)公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)