《iOS Drawing Practical UIKit Solutions》讀書筆記(二) —— The Language of Geometry
Points VS Pixels
Point是UIKit中的邏輯位置,並不代表畫素。主要是在Retina螢幕中,一個Point會對應2或3個Pixels,這取決於PPI(DPI):pixel per inch ,畫素密度PPI,指每英尺的畫素數,表示了清晰度。
Scale
UIScreen類的屬性scale, 表明了當前裝置點與畫素間的關係。因此,對於非Retain屏,scale == 1.0 ,而Retain屏則是2.0 或 3.0 。
UIScreen提供兩種屬性來表示display-size:
1. bounds : 表示Screen的bounding,以Point為單位,表示整個螢幕的大小。
2. applicationFrame: 表示實際可用螢幕的Frame,這是除掉status bars,navigation bars 或 tab bars的大小。
View Coordinates
在iOS 7及以後,view的座標原點一般都是在navigation bar下面開始。這取決於VC的edgesForExtendedLayout屬性設定。
Coordinate System之間的轉換
我們說所的Frame,Point,都是基於一個UIView中的座標系統來說的。如果想在UIView之間轉換座標值使得在另一個UIView中定位同樣的位置,iOS SDK 提供了一系列
toView, fromView方法。
注意,View之間的座標轉換是針對於位於同一個UIWIndow中的View來說的,只要位於一個View,就可以進行座標的轉換,而View之間不必有父子關係。
Key Structures
iOS 用四種主要的資料結構來描述幾何資訊,分別是:
- CGPoint
- CGSize
- CGRect
- CGAffineTransform
仿射變換,代表了a,b,c,d,tx和ty矩陣。
輸出Transforms
UIKit預設給出方法
NSStringFromCGAffineTransform()
CGAffineTransformFromString()
來使得NSString 和 CGAffineTransform之前轉換,但輸出的內容並不直觀:
我們可以用如下的函式來計算Scale和Rotate的數值:
CGFloat TransformGetXScale(CGAffineTransform t) {
return sqrt(t.a * t.a + t.c * t.c);
}
CGFloat TransformGetYScale(CGAffineTransform t) {
return sqrt(t.b * t.b + t.d * t.d);
}
CGFloat TransformGetRotation(CGAffineTransform t) {
return atan2f(t.b, t.a);
}
預定義常量
Conversion to Objects
由於所有的幾何物件都是以struct的形式,所以為了支援Object的方法,Core Graphics和Core Foundation均提供了若干函式將幾何物件轉換為真正的Object物件。
可以轉換為的物件是:
1. Strings
2. Dictionaries
3. Vaules
String
Dictionaries
注意,這裡轉換的Dictionary是CFDictionaryRef,需要bridge到NSDictionary
Value
NSValue為C型別的資料提供了一個面向物件的容器。它可以包含標量型別(int, float),指標和結構體。UIKit擴充套件了NSVaule,使之能夠接受Core Graphics資料。
Geometry Tests
Core Graphics提供了一系列函式,可以對幾何資料型別進行測試:
Rectangle 工具方法
針對CGRect結構,下面補充了一些工具方法,可用於平常開發時使用
1. 建立Rect
CGRect RectMakeRect(CGPoint origin, CGSize size) {
return (CGRect){.origin = origin, .size = size};
}
- 獲取Rect 的 Center Point
CGRect RectGetCenter(CGRect rect) {
return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
}
- 建立一個指定center point, size的Rect
CGRect RectAroundCenter(CGPoint center, CGSize size) {
CGFloat halfWidth = size.width / 2.0f;
CGFloat halfHeight = size.height / 2.0f;
return CGRectMake(center.x - halfWidth, center.y - halfHeight, size.width, size.height);
}
Fitting and Filling
有時候我們需要調整物件的大小,來讓它draw到比實際內容要大(小)的空間中。
常用的方法有:
1. centering
2. fitting
3. filling
4. squeezing
Centering
Centering的內容會顯示在矩形的正中,同時按照原始大小顯示。對於大出的部分,會被裁掉,而小的部分,其餘會預設填充灰色。
Fitting
Fitting意味著展示的內容會按照原比例壓縮(或放大),其餘的地方則會留空。
如圖
CGSize SizeScaleByFactor(CGSize aSize, CGFloat factor) {
return CGSizeMake(aSize.width * factor, aSize.height * factor);
}
CGFloat AspectScaleFit(CGSize sourceSize, CGRect destRect) {
CGSize destSize = destRect.size;
CGFloat scaleW = destSize.width / sourceSize.width;
CGFloat scaleH = destSize.height / sourceSize.height;
return MIN(scaleW, scaleH);
}
CGRect RectByFittingInRect(CGRect sourceRect, CGRect destinationRect){
CGFloat aspect = AspectScaleFit(sourceRect.size, destinationRect);
CGFloat targetSize = SizeScaleByFactor(sourceRect.size, aspect);
return RectAroundCenter(RectGetCenter(destinationRect), targetSize);
}
Filling
Filling 如圖所示,它會保證每一個畫素都會對應到目標空間的對應位置,但是卻不能保證圖片被顯示完全。為了實現畫素到目標空間的對應,原始圖片會被拉伸或縮小。
CGSize SizeScaleByFactor(CGSize aSize, CGFloat factor) {
return CGSizeMake(aSize.width * factor, aSize.height * factor);
}
CGFloat AspectScaleFill(CGSize soruceSize, CGRect destRect){
CGSize destSize = destRect.size;
CGFloat scaleW = destSize.width / sourceSize.width;
CGFloat scaleH = destSize.height / sourceSize.height;
return MAX(scaleW, scaleH);
}
CGRect RectByFillingRect(CGRect sourceRect, CGRect destinationRect){
CGFloat aspect = AspectScaleFill(sourceRect.size, destinationRect);
CGFloat targetSize = SizeScaleByFactor(sourceRect.size, aspect);
return RectAroundCenter(RectGetCenter(destinationRect), targetSize);
}
Squeezing
Squeezing會調整原始影象的大小與比例,使之能夠在目標空間中顯示出來。我們不需要對原始影象做任何調整,只需要將其draw到目的空間中,Quartz會做剩餘的事情。