iOS翻譯-Core Graphics教程1:入門
翻譯了一篇raywenderlich的文章,Core Graphics的入門教程。(有部分省略,剩下的都是主要過程。)
原文鏈接: http://www.raywenderlich.com/90690/modern-core-graphics-with-swift-part-1
想象一下你開發完成了你的app,并且運行良好,但是界面不太好看。你可以用Photoshop繪制多個size的控件圖片,希望Apple不會出@4x retina的屏幕。。
或者你可以提前預想到使用 Core Graphice 代碼創建一個image,這樣就能自動適應各種尺寸顯示在設備上。
Core Graphics 是Apple的矢量繪圖框架。它非常強大,API功能齊全,里面有許多需要學習的知識。但不用害怕,這里三部分可以引導你入門,最后你將在app里面創建一個好看的圖形。
這是一個全新的系列,用先進的方法來教開發者使用 Core Graphice 。這個系列全部在Xcode6用Swift寫(現在Xcode7了,測試可以通過),包含了新的功能,例如 @IBDesignable 和 @IBInspectable ,可以讓 Core Graphics 學起來更加有趣和容易。
帶著你最喜歡的飲料,讓我們開始吧!
介紹Flo - 每次喝一杯
你將創建一個完整的app來記錄你喝水的習慣。
這個app很容易記錄你喝了多少水。 它會告訴我們一天喝8杯水是健康的,但很容易在記錄幾杯后就忘記了。這就是寫Flo的原因。每次你干完一杯新鮮的水,點擊計數器。你也可以在里面看見7天的記錄。
在這部分里面,你將使用 UIKit 的繪制方法創建3個控件。
在第二部分,你將深入了解 Core Graphice 內容,繪制圖形。
在第三部分,你將創建帶圖案的背景,獎勵自己一枚自己繪制的金牌.
創建自定義視圖
當你想自定義繪制圖形的時候,你需要三個步驟:
- 1、創建 UIView 的子類
- 2、覆蓋 drawRect 方法,在里面寫一些繪制的代碼
- 3、沒有第三步了
讓我們嘗試做一個自定義的加號按鈕
先差創建一個button,命名為 PushButtonView
UIButton 是 UIView 的子類,所以在 UIButton 里面能夠使用 UIView 的所有方法,例如 drawRect
打開 Identity Inspector ,修改class為自定義的 PushButtonView
坐標和大小是X=250, Y=350, Width=100, and Height=100

增加約束
使用Auto Layout增加約束
會創建4個約束,你可以在 Size Inspector 里面看見下面的內容
移除默認的Button的title
繪制Button
首先需要明白3個原理,繪制圖片的路徑:
- 1、路徑是可以繪制和填充的
- 2、路徑輪廓的顏色是當前繪制的顏色
- 3、使用當前的填充顏色填滿封閉的路徑
創建 Core Graphice 路徑可以通過 UIBezierPath ,他提供了友好的API創建路徑。無論你想要線、曲線、矩形、還有一些列的連接點。
在 PushButtonView.swift 下面添加方法
override func drawRect(rect: CGRect) { var path = UIBezierPath(ovalInRect: rect) UIColor.greenColor().setFill() path.fill() }
這里用 ovalInRect 橢圓形方法,傳遞了一個矩形的大小。生成一個100*100的button在storyboard.所以橢圓形實際上是圓形。
路徑本身不知道如何繪制。你可以定義一個路徑但是不會繪制任何有用的內容。為了繪制路徑,你可以給當前內容一個填充的顏色(fill color)
運行程序將看見綠色的圓形.
到目前為止,你會發現創建一個自定義的圖形是多么容易,你已經創建了一個button的子類,覆蓋了 drawRect 方法,并且添加了 UIButton 在你的Storyboard上面
Core Graphics繪制的原理
每一個 UIView 都有一個 graphics context (繪圖上下文),在設備硬件顯示前,繪制的所有視圖都會被渲染到這個上下文中.
iOS 在任何時候需要更新視圖都是通過調用 drawRect 方法。發生在
- 1、視圖是在屏幕上是新的
- 2、頂部視圖被移除
- 3、視圖的hidden屬性改變
- 4、明確調用 setNeedsDisplay() 和 setNeedsDisplayInRect() 方法
注意:所有 drawRect 里面繪制,在完成之后會放到view 的graphics context中。如果你在 drawRect 外部繪制,你需要在最后面創建自己的graphics context
你還不必使用 Core Graphics 因為UIKit封裝了很多 Core Graphics 的方法。例如 UIBezierPath 封裝了 CGMutablePath (這是Core Graphics底層的API)
注意:不要直接調用 drawRect . 如果你需要更新視圖,調用 setNeedsDisplay() 方法
setNeedsDisplay() 不會自己調用 drawRect 方法,但是會標記視圖,讓視圖通過 drawRect 重繪在下一次循環更新的時候。 所以當你在一個方法里面多次調用 setNeedsDisplay() 的時候,你實際上也只是調用了一次 drawRect
@IBDesignable - 交互式繪制
代碼創建路徑去繪制,運行app去看結果看起來就像等顏料干一樣精彩。但是你有其他的選擇,Xcode6允許一個視圖通過 @IBDesignable 設置屬性。 可以在storyboard上面實時更新。
在 PushButtonView.swift ,在class聲明前添加 @IBDesignable
打開 Assistant Editor ,通過下面視圖查看
最后屏幕是這個樣子的
修改顯示的顏色,改成blueColor
UIColor.blueColor().setFill()
你會發現屏幕會立即改變
下面我們來添加”+”符號的線
繪制到Context
Core Graphics使用的是”畫家繪畫的模式”(原文是”Core Graphics uses a “painter’s model.”,一開始不太明白是什么意思,但是看下文的圖再來看這句話就明白是什么意思了)
當你畫一個內容的時候,就像在制作一幅畫。你繪制了一個路徑并且填滿它,然后你在這上面又繪制了另外一個路徑填滿它。 你不可能改變繪制的像素,但是你可以覆蓋他們。
下面這張圖片來自蘋果的官方文檔,描述了是如何工作的。正如你在一塊畫板上繪制圖片,決定樣式的是你繪制的順序

你的 + 號在藍色圓形的上面,所以首先你需要寫繪制藍色圓形的代碼,然后才是加號的繪制。
你可以畫兩個矩形實現加號,但是你同樣可以畫一個路徑然后用同樣的厚度描邊
修改 drawRect() 的代碼
//set up the width and height variables //for the horizontal stroke let plusHeight: CGFloat = 3.0 let plusWidth: CGFloat = min(bounds.width, bounds.height) * 0.6 //create the path var plusPath = UIBezierPath() //set the path's line width to the height of the stroke plusPath.lineWidth = plusHeight //move the initial point of the path //to the start of the horizontal stroke plusPath.moveToPoint(CGPoint( x:bounds.width/2 - plusWidth/2, y:bounds.height/2)) //add a point to the path at the end of the stroke plusPath.addLineToPoint(CGPoint( x:bounds.width/2 + plusWidth/2, y:bounds.height/2)) //set the stroke color UIColor.whiteColor().setStroke() //draw the stroke plusPath.stroke()
可以在storyboard中看到是這樣的結果
在iPad2和iPhone 6 Plus模擬器上運行。你會發現下面的情況
點和像素
點和像素占據相同的空間和大小,本質上是相同的事情。當retain的iPhone面世的時候,相同點上有4個像素
同樣的,iPhone6 Plus再一次把每個點的像素提升了。
具體學習可以參考 這篇 文章
這里有一個12 * 12 像素的宮格,點是灰色和白色的。iPad2是點和像素直接映射。iPhone6是2x retain屏幕,4個像素是一個點。第三個iPhone6 Plus 是3x retain屏幕,9個像素是一個點.
剛剛畫得線是3個點的高度。線從路徑的中間開始描繪,所以1.5個點會描繪在線的兩邊。
這個圖片展示了將回執3個點的線在設備上的情況。iPad2和iPhone6 Plus結果是需要跨越半個像素。iOS在兩種顏色中當一種顏色只有半邊像素填充的時候會抗鋸齒,所以線會看得模糊
實際的情況是,iPhone6 Plus有很多個像素,所以可能看不到模糊的情況。盡管如此,你需要檢查在真機上檢查你的app。但是假如你在不是retain的屏幕上(iPad2 or iPad mini),你可以避免抗鋸齒。
如果你的直線大小是單數,你應該把她們的點增加或減少0.5為了預防鋸齒。在iPad2上將移動半個像素點,在iPhone6上,剛好充滿整個像素。在iPhone6 Plus,剛好充滿1.5個像素.
修改后的代碼
//move the initial point of the path //to the start of the horizontal stroke plusPath.moveToPoint(CGPoint( x:bounds.width/2 - plusWidth/2 + 0.5, y:bounds.height/2 + 0.5)) //add a point to the path at the end of the stroke plusPath.addLineToPoint(CGPoint( x:bounds.width/2 + plusWidth/2 + 0.5, y:bounds.height/2 + 0.5))
iOS 將清晰的渲染直線在三個設備上,因為你改變了路徑的半個點
注意:為了線展現完美的像素,你可以用 UIBezierPath(rect:) 用fill填充,取代直接畫線。使用 contentScaleFactor 計算矩形的高度和寬度。 不像從路徑中心像兩邊描繪,fill只會向路徑里面填充 (這個東西好重要呀。。。)
接下來化垂直的線
//Vertical Line //move to the start of the vertical stroke plusPath.moveToPoint(CGPoint( x:bounds.width/2 + 0.5, y:bounds.height/2 - plusWidth/2 + 0.5)) //add the end point to the vertical stroke plusPath.addLineToPoint(CGPoint( x:bounds.width/2 + 0.5, y:bounds.height/2 + plusWidth/2 + 0.5))
結果是這樣子的
@IBInspectable 自定義Storyboard屬性
@IBInspectable 定義的屬性能夠在IB里面可見。這意味著你可以不用代碼,在IB里面設置button的顏色
@IBInspectable var fillColor: UIColor = UIColor.greenColor() @IBInspectable var isAddButton: Bool = true
在 drawRect 里面修改
UIColor.blueColor().setFill()
變成
fillColor.setFill()
最后修改好的代碼是
import UIKit @IBDesignable class PushButtonView: UIButton { @IBInspectable var fillColor: UIColor = UIColor.greenColor() @IBInspectable var isAddButton: Bool = true override func drawRect(rect: CGRect) { var path = UIBezierPath(ovalInRect: rect) fillColor.setFill() path.fill() //set up the width and height variables //for the horizontal stroke let plusHeight: CGFloat = 3.0 let plusWidth: CGFloat = min(bounds.width, bounds.height) * 0.6 //create the path var plusPath = UIBezierPath() //set the path's line width to the height of the stroke plusPath.lineWidth = plusHeight //move the initial point of the path //to the start of the horizontal stroke plusPath.moveToPoint(CGPoint( x:bounds.width/2 - plusWidth/2 + 0.5, y:bounds.height/2 + 0.5)) //add a point to the path at the end of the stroke plusPath.addLineToPoint(CGPoint( x:bounds.width/2 + plusWidth/2 + 0.5, y:bounds.height/2 + 0.5)) //Vertical Line if isAddButton { //move to the start of the vertical stroke plusPath.moveToPoint(CGPoint( x:bounds.width/2 + 0.5, y:bounds.height/2 - plusWidth/2 + 0.5)) //add the end point to the vertical stroke plusPath.addLineToPoint(CGPoint( x:bounds.width/2 + 0.5, y:bounds.height/2 + plusWidth/2 + 0.5)) } //set the stroke color UIColor.whiteColor().setStroke() //draw the stroke plusPath.stroke() } }
isAddButton 的設置可以標識是否需要添加豎線,也就是表明是加號還是減號
改變fill顏色RGB(87, 218, 213), isAddButton 為off
顯示出來的結果是
UIBezierPath 畫圓弧
下面我們自定義的視圖是這樣子的
這看起來像一個填充的形狀,但是這個圓弧實際上是一個大的描邊。外部的線是另外一個路徑的描邊組成的2個圓弧。
創建一個 CounterView 類,這個事UIView的子類
import UIKit let NoOfGlasses = 8 let π:CGFloat = CGFloat(M_PI) @IBDesignable class CounterView: UIView { @IBInspectable var counter: Int = 5 @IBInspectable var outlineColor: UIColor = UIColor.blueColor() @IBInspectable var counterColor: UIColor = UIColor.orangeColor() override func drawRect(rect: CGRect) { } }
NoOfGlasses :是一個數字表明每天喝水的杯數。
counter : 記錄了喝水的杯數
在剛剛PushButtonView上面放置一個視圖,所屬類是 CounterView ,坐標大小是
數學知識
畫這個圓弧需要根據單位園來畫
紅色箭頭表示開始與結束的點,順時針繪畫。 從3π/4弧度開始畫。相當于135°,順時針到π/4弧度,也就是45°
畫弧度
在 CounterView.swift 里面, drawRect 方法
// 1 let center = CGPoint(x:bounds.width/2, y: bounds.height/2) // 2 let radius: CGFloat = max(bounds.width, bounds.height) // 3 let arcWidth: CGFloat = 76 // 4 let startAngle: CGFloat = 3 * π / 4 let endAngle: CGFloat = π / 4 // 5 var path = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2, startAngle: startAngle, endAngle: endAngle, clockwise: true) // 6 path.lineWidth = arcWidth counterColor.setStroke() path.stroke()
- 1、設置中心點
- 2、計算視圖最大尺寸的半徑
- 3、計算扇形的厚度
- 4、設置開始和結束的弧度
- 5、根據中心點、半徑、還有度數畫路徑
- 6、設置線的寬度和顏色,最后把路徑繪制出來。
注意:這里有畫弧的更詳細的介紹 Core Graphics Tutorial on Arcs and Paths
最后實現的效果是
圓弧的輪廓
//Draw the outline //1 - first calculate the difference between the two angles //ensuring it is positive let angleDifference: CGFloat = 2 * π - startAngle + endAngle //then calculate the arc for each single glass let arcLengthPerGlass = angleDifference / CGFloat(NoOfGlasses) //then multiply out by the actual glasses drunk let outlineEndAngle = arcLengthPerGlass * CGFloat(counter) + startAngle //2 - draw the outer arc var outlinePath = UIBezierPath(arcCenter: center, radius: bounds.width/2 - 2.5, startAngle: startAngle, endAngle: outlineEndAngle, clockwise: true) //3 - draw the inner arc outlinePath.addArcWithCenter(center, radius: bounds.width/2 - arcWidth + 2.5, startAngle: outlineEndAngle, endAngle: startAngle, clockwise: false) //4 - close the path outlinePath.closePath() outlineColor.setStroke() outlinePath.lineWidth = 5.0 outlinePath.stroke()
- 1、 outlineEndAngle 是輪廓結束的度數。根據當前的 counter 來計算
- 2、 outlinePath 是輪廓的路徑。
- 3、添加一個內置的圓弧。有相同的度數,不過要反著來繪畫(clockWise要設置為false)
- 4、自動閉合路徑,畫線。
最后的結果是這樣的
讓它工作起來
在Storyboard里面調整Counter Color為 RGB(87, 218, 213) ,Outline Color為 RGB(34, 110, 100)
在 ViewController.swift 里面增加這些屬性和方法
//Counter outlets @IBOutlet weak var counterView: CounterView! @IBOutlet weak var counterLabel: UILabel!
@IBAction func btnPushButton(button: PushButtonView) { if button.isAddButton { counterView.counter++ } else { if counterView.counter > 0 { counterView.counter-- } } counterLabel.text = String(counterView.counter) }
在 viewDidLoad 里面設置counterLabel的更新值
counterLabel.text = String(counterView.counter)
最后在Storyboard里面連線

為了能夠點擊按鈕后能夠重新繪制,需要修改 CounterView 里面的 counter 屬性的setter方法,調用 setNeedsDisplay
@IBInspectable var counter: Int = 5 { didSet { if counter <= NoOfGlasses { //the view needs to be refreshed setNeedsDisplay() } } }
最后app可以運行啦
總結:
1、學習了繪圖的基本原理
2、如何使用 @IBDesignable 和 @IBInspectable3、抗鋸齒問題是如何解決的
4、繪圖的順序,以及扇形的基本知識,如何去繪制扇形
來自: http://www.liuchendi.com/2016/01/07/iOS/31_CoreGraphics_1/