本文翻譯自Swift: CGRect, CGSize & CGPoint
在我轉向Swift後,我逐漸避免寫出具有OC風格的swift程式碼並開始真正利用上這門語言的優點。
但最近我發現在處理CGGeometry
結構體時,我依然使用了醜陋的,非Swift風格的程式碼。CGGeometry
結構體指的是:
CGRect, CGSize, CGPoint
C風格語法,披著狼皮的羊
我有一種強烈的感覺,許多Swift程式設計師都會對此感到內疚,請寫過下面這種程式碼的同學舉手:
let rect = CGRectMake(0, 0, 100, 100)
let point = CGPointMake(0, 0)
let size = CGSizeMake(100, 100)
複製程式碼
別擔心,這並不是什麼應該羞愧的事情。
這麼寫的問題在於它不符合Swift的語法風格,雖然可以正常執行,但是它看上去更像是一段OC,甚至是Java程式碼。
一個有經驗的iOS開發者一眼就能看出這幾行程式碼的含義,他們不需要CGGeometry
結構體的初始化函式中的引數名。但Swift希望對所有新手很友好,如果他們第一眼看到這些程式碼一定會不知所措,因為他們無法理解這些數字的含義。因此,我們應該使用Swift版本的程式碼:
let rect = CGRect(x: 0, y: 0, width: 100, height: 100)
let size = CGSize(width: 100, height: 100)
let point = CGPoint(x: 0, y: 0)
複製程式碼
儘管程式碼變得略微冗長一些,但是它現在具備良好的可讀性。而且另一個額外的好處在於,引數不必侷限於CGFloat
型別了,我們還可以使用Int
和Double
型別的引數。檢視一下CGRect
結構體的定義和CGRectMake
函式的定義就很容理解了:
extension CGRect {
public init(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat)
public init(x: Double, y: Double, width: Double, height: Double)
public init(x: Int, y: Int, width: Int, height: Int)
}
public func CGRectMake(x: CGFloat,
_ y: CGFloat,
_ width: CGFloat,
_ height: CGFloat) -> CGRect
複製程式碼
Zero
你或許還在使用這樣的程式碼:
let rect = CGRectZero
let size = CGSizeZero
let point = CGPointZero
複製程式碼
是時候更新一下自己的知識體系,改用swift風格的語法了。別擔心,新的語法只需要增加一個字元:
let rect = CGRect.zero
let size = CGSize.zero
let point = CGPoint.zero
複製程式碼
這樣寫的好處在於Xcode的程式碼高亮機制會將.zero
高亮顯示,這樣它會更醒目,降低你的認知負荷。
獲取值
如果你曾經或任然是一名優秀的OC開發者,你應該寫過這樣的程式碼來獲取結構體中的值:
CGRect frame = CGRectMake(0, 0, 100, 100)
CGFloat width = CGRectGetWidth(frame)
CGFloat height = CGRectGetHeight(frame)
CGFloat maxX = CGRectGetMaxX(frame)
CGFloat maxY = CGRectGetMaxY(frame)
複製程式碼
不過不妨先思考一下,為什麼不能直接獲取值呢?比如這樣:
CGFloat width = frame.size.width
CGFloat height = frame.size.height
複製程式碼
For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.
(譯者注:)考慮兩個CGRect,第一個origin
是[0,0],size
是[10, 10]。第二個的origin
是[10,10],size
是[-10, -10]。兩者其實是等價的,無論是OC還是Swift都支援這兩種寫法。問題在於,在OC中,獲取結構體的屬性就直接獲取到它的真實值了,這個值有可能為負。所以OC建議我們呼叫系統API而不是直接獲取值,而在Swift中,width
、height
等被設計為計算屬性,自然就不存在這樣的問題了。
由於系統提供了完備的API,很多人覺得不直接獲取值也不是什麼問題。不過Swift提供了簡單的點語法表示式,將我們從這種不美觀的API中解放出來:
let frame = CGRect(x: 0, y: 0, width: 100, height: 100)
let width = frame.width
let height = frame.height
let maxX = frame.maxX
let maxY = frame.maxY
複製程式碼
可變性
let frame = CGRect(x: 0, y: 0, width: 100, height: 100)
let view = UIView(frame: frame)
view.frame.origin.x += 10
複製程式碼
現在,你不僅可以直接修改結構體中的某個值,還可以直接替換整個子結構體:
let view = UIView(frame: .zero)
view.frame.size = CGSize(width: 10, height: 10)
view.frame.origin = CGPoint(x: 10, y: 10)
複製程式碼
單單這個特性就足夠我們放棄OC,投入Swift的懷抱了。我們不必非得重新建立一個結構體例項再修改了,不到兩年前,我們OC開發者還被迫寫下這樣的程式碼:
CGRect frame = CGRectMake(0, 0, 100, 100);
UIView *view = [[UIView alloc] initWithFrame: frame];
CGRect newFrame = view.frame;
newFrame.size.width = view.frame.origin.x + 10;
view.frame = newFrame;
複製程式碼
我不知道你怎麼想,但是寫出這樣的程式碼快要把我逼瘋了。根據view的frame
重新建立結構體,修改它,然後再把view的frame
改成這個結構體,再見了,再也不見!
最後說一句
以上這些改變還適用於UIKit中的其他結構體:
// UIEdgeInsets
var edgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
edgeInsets.top += 10
// UIOffset
var offset = UIOffset(horizontal: 10, vertical: 10)
offset.vertical += 10
複製程式碼
示範程式碼可以在作者的github上找到