本文翻譯自Swift: CGRect, CGSize & CGPoint
譯者:bestswifter
在我轉向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.— Apple, CGGeometry Reference Documentation
(譯者注:)考慮兩個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上找到