使用.transform旋轉帶來的坑
這禮拜都在玩樹莓派,Xcode都幾乎沒打開過,該收收心了。OmniFocus里攢了一堆已過期的待辦,feedly也有好多未讀,都又到周末了,先把上個禮拜留下來的幾個準備寫的Topic寫了吧。
關于UIView的旋轉,踩了一個小坑,看了看也沒人說過。大家都知道使用.transform可以做旋轉,用起來也很簡單。在我的UIView Demo中(見上周Blog UIController中Slider監聽回調具體實現的分離 ),我也添加了旋轉的功能,開始是在handler中這么寫的(back是一個UIView):
let x = (Float(ctl.show.frame.width) - conf[0] )/2 let y = (Float(ctl.show.frame.height) - conf[1] )/2 back.frame.size = CGSize(width: CGFloat(conf[0], height: CGFloat(conf[1]) back.frame.origin = CGPoint(x: Int(x), y: Int(y)) back.transform = CGAffineTransformMakeRotation( CGFloat(Double(conf[7] * M_PI / 180.0))
前面幾句修改大小和位置,最后一句做旋轉。沒添加旋轉之前縮放和位移都是對的,添加了旋轉之后效果卻不是我想象的那樣以圖形中心為圓心轉動,而是這樣有時候會自己拉扁的:
添加了打印信息以后發現,經過旋轉的View.frame,不管是size還是position都變掉了,并且完全不是按照希望的樣子變化的。不論我把旋轉放在整個片段的哪里,都不對,frame的size總是不受控制。后來只好把scaling的部分改成了CGAffineTransformScale()。去看了看transform系列的文檔,它其實是一類矩陣變換方式,旋轉、縮放、平移只是其中的一些常用算法,系統給出了這些功能的API。如果你數學夠好,其實也是可以自己設置這種變換的映射方式,最后轉換后的View,其frame size是包含目標圖形的一個最小矩形。而我猜,我碰到的問題,是因為系統對于旋轉的處理有點特殊,因為scaling時拿到的參數不正確(當時的frame還沒有畫成縮放后的樣子)導致。
于是我將UIView的變化都改為用CGAffineTransform系列函數進行,又發現對transform的操作先后順序會影響最終的效果。出于興趣我在playground里面做了點測試,先申請了一個100*300的矩形,然后做了兩個transform的參數分別表示旋轉90度和縮放至200*200(長寬縮放比例為1:2和1:0.667):
</div>
let demo = UIView(frame:CGRectMake(0.0, 0.0, 100.0, 300.0)) //縮放參數 let sx = 200 / demo.frame.width let sy = 200 / demo.frame.height let transf_scale = CGAffineTransformMakeScale(sx, sy) //旋轉參數 let transf_rotate = CGAffineTransformMakeRotation(CGFloat(Double(90) * M_PI / 180.0))
對這個View分別進行縮放和旋轉操作,理想的情況是變成一個200*200的正方形。四個測試Case分別如下:兩個先縮放再旋轉,兩個先旋轉再縮放,后兩個用的是CGAffineTransformConcat()函數直接跑。
//case 1, rotate(scale, x) demo.transform = CGAffineTransformRotate(transf_scale, CGFloat(Double(90) M_PI / 180.0)) demo.frame.size //60067//case 2, scale(rotate, x) demo.transform = CGAffineTransformScale(transf_rotate, sx, sy) demo.frame.size //200*200
//case 3, scale+rotate demo.transform = CGAffineTransformConcat(transf_scale, transf_rotate) demo.frame.size //200*200
//case 4, rotate+scale demo.transform = CGAffineTransformConcat(transf_rotate, transf_scale) demo.frame.size //60067</pre>
有興趣的可以把這一段code放到playground里面自己跑跑,看看會是什么結果。case2 & case3,結果如我們所想象的那樣,是個200200的正方形;而case1 & case4,卻變成了600*66.7的長條矩形。看起來正如下圖的幾種情況:</p>
![]()
圖中,暗紅色部分是先做縮放再做旋轉,有了正確的結果,對應scale(rotate, x) & scale+rotate;銀色部分則是先旋轉再縮放,成了錯誤的樣子,對應rotate+scale & rotate(scale, x)。原來這是有順序要求的。那么問題又來了:CGAffineTransformXXX(t, args)系列的函數,感覺上應該是對t用新的參數做疊加,為什么結果卻是反的呢?于是我去查了它們的頭文件,看到是這么定義的:
</div>
/* Scale t’ by (sx, sy)’ and return the result:
t’ = [ sx 0 0 sy 0 0 ] * t */
@available(iOS 2.0, *)
public func CGAffineTransformScale(t: CGAffineTransform, _ sx: CGFloat, _ sy: CGFloat) -> CGAffineTransform
/* Rotate t’ by angle’ radians and return the result:
t’ = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] * t */
@available(iOS 2.0, *)
public func CGAffineTransformRotate(t: CGAffineTransform, _ angle: CGFloat) -> CGAffineTransform
/* Concatenate t2’ to t1’ and return the result:
t’ = t1 * t2 */
@available(iOS 2.0, *)
public func CGAffineTransformConcat(t1: CGAffineTransform, _ t2: CGAffineTransform) -> CGAffineTransform
</div>
于是真相大白:原來, CGAffineTransformScale(t1, args)= t-scale*t1,而CGAffineTransformConcat(t1, t2)= t1 *t2,它們的順序是反的 。這個坑有點繞得人人暈暈的:flushed:
最后,綜上所述,對于transform的使用,我總結了以下幾點:
- transform參數,事實上是一些形變映射矩陣的疊加。
- Scale的時候設置的參數,代表的是縮放的系數,不是目標size,所以如果有其他transform參數在此之前做,要重新計算系數
- CGAffineTansform系列本質是矩陣運算,順序很重要
- CGAffineTransformConcat(t1,t2)的順序是t1->t2, 而CGAffineTransformXXX(t1, (args to t2))的順序是t2->t1,不要搞錯
</ol> </div>
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!相關經驗
相關資訊