iOS翻譯-Core Graphics教程1:入門

jopen 8年前發布 | 14K 次閱讀 iOS開發 移動開發

翻譯了一篇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 和 @IBInspectable

3、抗鋸齒問題是如何解決的

4、繪圖的順序,以及扇形的基本知識,如何去繪制扇形

來自: http://www.liuchendi.com/2016/01/07/iOS/31_CoreGraphics_1/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!