你知道嗎, CoreGraphics繪圖系統和Bezier貝塞爾曲線座標系的順時針方向是相反的!

滴水微瀾發表於2018-10-26
UIBezierPath是對Core Graphics框架的一種上層封裝,目的是讓繪圖需求可以被更方便的使用。
那你有沒有發現被UIBezierPath封裝後與之前有什麼改變?
答:有三個變化。
1.遮蔽繁雜重複的內容
2.功能閹割
3.座標系順時針方向反轉
 
證明1:遮蔽繁雜重複的內容
相比Core Graphics框架,UIBezierPath幫我們做了一些繁瑣的事件。比如有這樣一個場景:需要畫一個圓,但是它的每個1/4弧線的strokpath顏色是不同的。對於這樣的需求。
有個錯誤的做法是:
1.拿到上下文
2.設定第一個1/4戶的strokpath顏色,用上下文繪製第一個1/4弧
3.設定第一個2/4戶的strokpath顏色,用上下文繪製第一個2/4弧
4.設定第一個3/4戶的strokpath顏色,用上下文繪製第一個3/4弧
5.設定第一個4/4戶的strokpath顏色,用上下文繪製第一個4/4弧
最後的結果會發現,這四段弧的顏色是最後一個4/4弧的strokpath的顏色
原因是:對於一個上下文來說,strokPathColor屬性只有一個,雖然設定了4次,但總是後面的覆蓋前面的。
 
一種解決方法是:
在第2步之前,先迴圈4次操作
let content = UIGraphicsGetCurrentContext()
content?.setStrokeColor(UIColor.blue.cgColor)
content?.saveGState()
然後在每一步繪製前,恢復上下文棧中的儲存到當前上下文
 
content?.restoreGState()

 

另一種解決方法是:
直接建立4個UIBezierPath,用貝塞爾曲線繪製著4段弧。
這樣就很直觀的看出,每個UIBezierPath的上下文都是獨立的。內部幫我們自己做了上下文的存棧和出棧。
 
證明2:功能閹割
雖然有了UIBezierPath的封裝我們用起來方便了,但是相應的代價是所提供的功能被閹割了。有些強大的功能UIBezierPath沒有提供實現,比如:現在要畫一個圓形的漸變球,就只能使用Core Graphics框架。
程式碼如下
//利用上下文繪製漸變色(圓形)
let context = UIGraphicsGetCurrentContext()
//顏色空間
let colorSpace = CGColorSpaceCreateDeviceRGB()
let startColor = UIColor.black
let endColor = UIColor.red
//顏色陣列
let colors = [startColor.cgColor,endColor.cgColor]
//顏色所處位置
let locations:[CGFloat] = [0,1]
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as! CFArray, locations: locations)
let center = CGPoint(x: rect.size.width*0.5, y: rect.size.height*0.5)
let radius = rect.size.height*0.3
context?.drawRadialGradient(gradient!, startCenter: center, startRadius: radius*0.2, endCenter: center, endRadius: radius, options: CGGradientDrawingOptions.drawsBeforeStartLocation)

 

證明3:座標系順時針方向反轉
你知道嗎, CoreGraphics繪圖系統和Bezier貝塞爾曲線座標系的順時針方向是相反的!
 
我記得上學時老師講的座標系是這樣的: 

X軸指向右側,Y軸指向上面。對應的弧度如圖上標的那樣。順時針也是鐘錶錶針轉動的方向。這就是最早接觸的座標系,熟悉的單純模樣。

 

在工作時,當我們往螢幕上佈局UI時,用到的座標系是下面這樣:

對於UI控制元件來講的座標系模式,X軸方向向右,Y軸方向向下。
請注意弧度值也相應的轉了方向,它是沿著X,Y指向的方向開始逐漸增加的。
順時針還是熟悉的鐘表表針轉動方向。
 
關鍵程式碼如下:
        let content = UIGraphicsGetCurrentContext()

        var endAngl = _progressValue*CGFloat(M_PI*2)
        var clockState = (_direction == .onTime)
       
        //
        var des: String = ""
        des = clockState ? "UIGraphics上下文繪製、順時針" : "UIGraphics上下文繪製、逆時針”

        content?.move(to: CGPoint(x: width-arcRadius, y: height*0.5))
        let bez = UIBezierPath(arcCenter: arcCenter, radius: arcRadius, startAngle: 0, endAngle: endAngl, clockwise: clockState)
            content?.addPath(bez.cgPath)

        NSString(string: des).draw(in: CGRect(x: 2, y: 2, width: width*0.4, height: height*0.5), withAttributes: atts)
        log = String(format: "繪製弧度: %.4f Pi", endAngl/3.14)
        
        content?.strokePath()

實際效果圖如下:

UIBezierPath順時針模式下,從0到2PI的效果
 
UIBezierPath逆時針模式下,從0到2PI的效果

 

然後突出的CoreGraphics表示不服,我就要與眾不同。如下圖:

說出來你可能不信,你會發現順時針方向往上了。這明明是逆時針方向啊!WTF?

來看下程式碼和實現效果吧。

       let content = UIGraphicsGetCurrentContext()

        var endAngl = _progressValue*CGFloat(M_PI*2)
        var clockState = (_direction == .onTime)
       
        //圓
        var des: String = ""
        des = clockState ? "UIGraphics上下文繪製、順時針" : "UIGraphics上下文繪製、逆時針”

        content?.move(to: CGPoint(x: width-arcRadius, y: height*0.5))
        content?.addArc(center: arcCenter, radius: arcRadius, startAngle: 0, endAngle: endAngl, clockwise: clockState)

        NSString(string: des).draw(in: CGRect(x: 2, y: 2, width: width*0.4, height: height*0.5), withAttributes: atts)
        log = String(format: "繪製弧度: %.4f Pi", endAngl/3.14)
        
        content?.strokePath()

 

實際效果如下:

CoreGraphics順時針模式下,從0到2PI的效果

 

CoreGraphics逆時針模式下,從0到2PI的效果

 

CoreGraphics和Bezier貝塞爾曲線都是平時開發中的利器,認真品味一下兩者的區別,會讓我們對它們有更深的認識。

有講的不對的地方歡迎指正。

Demo地址:https://github.com/zhfei/CoordinateSystem

 

相關文章