Swift Core Graphics教程之Gradients 與 Context

FinalApp

更新時間 04/15/2015 爲Xcode 6.3 和 Swift1.2更新

歡迎回到我們的Swift核心繪圖教程系列!

第一部分中,你學習到了使用storyboard繪製線條和矩形.

在第二部部分中,你將深入核心繪圖,使用CGContext實現漸變效果

核心繪圖

你現在已經從簡單的UIKit深入到核心繪圖。

下圖是各個框架的關係圖:

2-Architecture

UIKit是在最頂層,使用最友好的框架。你使用過UIBezierPath的就是在UIKit層中對Core Graphics層中CGPath的封裝。

你可以看到 Core Graphics 的對象和方法都是CG開頭的,非常容易辨認。

另外CG方法都是C方法,在調用的時候不需要明確的指定參數名,和一般Swift調用方法不一樣。

從Graph View開始

本次的目標就是使用歷史數據創建一個圖表。

2-ResultGraphView

在繪圖之前,你需要創建一個storyboard,寫一些顯示圖表的代碼。

完成後的視圖結構如下圖:

2-ViewHierarchy

如果你還沒有完成,從這裏下載開始的項目。

打開 File\New\File…, 選擇 iOS\Source\Cocoa Touch Class
模板然後點擊 Next. 輸入名稱 GraphView,選擇成爲 UIView 的子類,選擇語言 Swift。 點擊 NextCreate.

打開 Main.storyboard 拖一個 UIView 到控制器的視圖。

視圖將包含一個GraphCounter Views,確定他們是視圖控制器的主視圖的子視圖,並且GraphCounter Views之上。

此時目錄結構應該是這樣的:

2-DocumentOutline

Size Inspector, 設置 X=150, Y=50, Width=300,
Height=300:

2-ContainerCoordinates

創建自動佈局約束非常簡單,在第一部分中有講到。

  • 選中視圖, 按住Control輕輕的向左拖動,在彈出的菜單中選擇Width.
  • 選中視圖, 按住Control輕輕的向上拖動,在彈出的菜單中選擇Height.
  • 選中視圖, 按住Control從裏向外拖動,在彈出的菜單中選擇 Center Vertically in Container.
  • 選中視圖, 按住Control從裏向外拖動,在彈出的菜單中選擇 Center Horizontally in Container.

當創建了視圖,通常我們我們會設置一個臨時的背景顏色,這樣做非常有用,可以讓我們清楚的看到我們做了什麼。

Attributes Inspector, 將背景顏色設置爲黃色.

2-ContainerViewBackground

再拖一個 UIView 到黃色的視圖作爲它的子視圖.

Identity Inspector, 將新創建的視圖的class設置爲GraphView.

2-GraphViewClass

Size Inspector, 設置 X=0, Y=25, Width=300,
Height=250:

2-GraphViewCoordinates

Document Outline,將 Counter View拖到黃色的視圖中作爲它的子視圖,這裏需要注意需要放置在Graph View的後方。

在移動 Counter View之後, 自動佈局約束會變紅. 選擇 Counter View, 在storyboard 右下方找到並點擊 Resolve Auto Layout Issues, 選擇
Selected Views: Clear Constraints.

2-ClearAutoLayout

你可以重置爲默認的約束,因爲現在Counter View充滿了整個Container View

Document Outline 雙擊黃色視圖來重命名爲 Container View. 現在 Document Outline 是這樣子的:

Flo2-Outline

你需要一個Container View是因爲在 Counter ViewGraph View的漸變動畫中需要用到。

打開 ViewController.swift ,爲ContainerGraph Views 添加outloets:


@IBOutlet weak var containerView: UIView!
@IBOutlet weak var graphView: GraphView!

這一步爲container viewgraph view創建outlet,現在我們將他們和storyboard的視圖關聯起來。

打開 Main.storyboard, 將 Graph View Container View 和 outlets關聯起來:

Flo2-ConnectGraphViewOutlet

設置漸變動畫

仍然在 Main.storyboard中, 從Object Library 拖一個UITapGestureRecognizerContainer View:

Flo2-AddTapGesture

打開 ViewController.swift 在開頭添加屬性:


var isGraphViewShowing = false

這個將簡單的標記用於判斷當前graph view是否正在顯示;

爲點擊事件添加過渡效果的方法:


@IBAction func counterViewTap(gesture:UITapGestureRecognizer?) {
if (isGraphViewShowing) {
 
//hide Graph
UIView.transitionFromView(graphView,
toView: counterView,
duration: 1.0,
options: UIViewAnimationOptions.TransitionFlipFromLeft
UIViewAnimationOptions.ShowHideTransitionViews,
completion:nil)
} else {
 
//show Graph
UIView.transitionFromView(counterView,
toView: graphView,
duration: 1.0,
options: UIViewAnimationOptions.TransitionFlipFromRight
UIViewAnimationOptions.ShowHideTransitionViews,
completion: nil)
}
isGraphViewShowing = !isGraphViewShowing
}

UIView.transitionFromView(_:toView:duration:options:completion:)
設置了一個水平翻動的效果. 另外還有擴散,溶解,豎直翻動,彎曲和下降等效果。 過渡效果使用ShowHideTransitionViews 係數, 你不必通過移除視圖來使它消失。

btnPushButton(_:)的最後添加以下代碼:


if isGraphViewShowing {
counterViewTap(nil)
}

這是爲了防止在圖標正在顯示的過程中點擊,將閃回計數器視圖。

最後,爲了讓這個效果生效,打開 Main.storyboard,將點擊手勢與counterViewTap(gesture:)關聯起來。

Flo2-TapGestureConnection

構建並運行程序,你可以看到graph view在你打開程序的時候。點擊它,graph view會消失,counter view現實,中間有過渡動畫。

2-ViewTransition

分析Graph View

2-AnalysisGraphView

還記得第一部分的繪圖模型嘛?它描述了用Core Graphics繪圖從裏到外的過程,你需要明白這裏的順序在你寫代碼之前:

  1. 漸變的背景
  2. 在圖表中進行裁剪
  3. 折線
  4. 折線點
  5. 橫線
  6. 標籤

繪製漸變

現在我們在Graph Viewcontext中繪製漸變效果。

打開 GraphView.swift 將裏面帶代碼替換爲以下:


import UIKit
 
@IBDesignable class GraphView: UIView {
 
//1 - the properties for the gradient
@IBInspectable var startColor: UIColor = UIColor.redColor()
@IBInspectable var endColor: UIColor = UIColor.greenColor()
 
override func drawRect(rect: CGRect) {
 
//2 - get the current context
let context = UIGraphicsGetCurrentContext()
let colors = [startColor.CGColor, endColor.CGColor]
 
//3 - set up the color space
let colorSpace = CGColorSpaceCreateDeviceRGB()
 
//4 - set up the color stops
let colorLocations:[CGFloat] = [0.0, 1.0]
 
//5 - create the gradient
let gradient = CGGradientCreateWithColors(colorSpace,
colors,
colorLocations)
 
//6 - draw the gradient
var startPoint = CGPoint.zeroPoint
var endPoint = CGPoint(x:0, y:self.bounds.height)
CGContextDrawLinearGradient(context,
gradient,
startPoint,
endPoint,
0)
}
}

這裏有以下步驟

  1. 將漸變的開始和結束顏色設置爲@IBInspectable 屬性,這樣就可以在storyboard中實時看到效果.
  2. CG 繪圖方法需要知道他們在哪個上下文(context)中繪圖,,調用 UIGraphicsGetCurrentContext() 來獲取當前上下文. 那就是 drawRect(_:) 繪製的地方.
  3. 所有的上下文都有一個色域. 可以是 CMYK 也可以是 grayscale,
    這裏使用的是RGB色域.
  4. 顏色的起止描述了顏色的變化效果. 在這個例子中, 你只有兩種顏色,從紅到綠,
    你也可以又一個數組的顏色變化, 顏色從紅到藍在到綠. 每種顏色的起止都是0.33.
  5. 創建漸變效果,定義色域,顏色起止。
  6. 最後,繪製漸變效果通過方法CGContextDrawLinearGradient()
    有以下幾個參數:
    • CGContext表示在哪個上下文
    • CGGradient 色域,顏色起止。
    • 開始點
    • 結束點
    • 其他延伸選項

通過drawRect(_:)漸變效果將充滿整個rect

在Xcode你通過設置代碼或者在storyboard的Assistant Editor調節,可以直接在Graph View看到效果。

2-InitialGradient

在storyboard中, 選擇 Graph View. 在 *Attributes
Inspector*設置 Start Color 爲 RGB(250, 233, 222), 設置 End Color 爲RGB(252, 79, 8):

2-FirstGradient

Main.storyboard, 一次選擇所有的視圖,除了控制器自帶的,將他們的BackgroundColor 設置爲clear color. 現在已經不需要黃色背景了,同樣將按鈕的背景顏色設置爲透明.

運行程序,你會發現現在變得好看很多。

裁剪區域

你剛纔用漸變填充了整個上下文的區域。現在你可以創建路徑來裁剪當前的繪圖區域。

打開GraphView.swift, 在drawRect(_:)的頂部加入以下代碼:


let width = rect.width
let height = rect.height
 
//set up background clipping area
var path = UIBezierPath(roundedRect: rect,
byRoundingCorners: UIRectCorner.AllCorners,
cornerRadii: CGSize(width: 8.0, height: 8.0))
path.addClip()

這個將創建一個裁剪區域來限制漸變效果,你可以用相同的方式繪製漸變效果在在折線上。

構建運行程序,你會發現有漂亮的圓角效果:

2-RoundedCorners2

注意: 繪製靜態的圖像使用Core Graphics是綽綽有餘的,但是當你的圖像需要頻繁的重繪時,你應當使用Core Animation layers,它充分利用了GPU而不是CPU。CPU是通過調用 drawRect(_:)繪製的。

除了使用裁剪路徑,你還可以通過使用CALayer的cornerRadius屬性來創建圓角,但是你需要優化。爲了更好的理解,可以參考Mikael Konutgan and Sam Davies寫的Custom Control Tutorial for iOS and Swift: A Reusable
Knob
,使用Core Animation來創建自定義控件。

計算座標點

現在可以短暫了停一下畫圖,我們需要繪製7個點,橫座標爲一週的七天,縱座標爲

首先, 設置幾個簡單的值。

GraphView.swift, 在頂部添加屬性:


//Weekly sample data
var graphPoints:[Int] = [4, 2, 6, 4, 5, 8, 3]

這裏簡單的數據代表了七天的數據。

接着,在drawRect(_:)的最後添加代碼:


//calculate the x point
 
let margin:CGFloat = 20.0
var columnXPoint = { (column:Int) -> CGFloat in
//Calculate gap between points
let spacer = (width - margin*2 - 4) /
CGFloat((self.graphPoints.count - 1))
var x:CGFloat = CGFloat(column) * spacer
x += margin + 2
return x
}

橫座標點包括了7個等距離的點。上述代碼在一個閉包表達式中,應該是要獨立的封裝成一個方法,但是對於這個小的運算量,是可以內嵌在代碼之中。

columnXPoint 接受 column作爲參數, 返回點所在的橫座標的值.

接着,在drawRect(_:)的最後添加代碼來計算縱座標的點:


// calculate the y point
 
let topBorder:CGFloat = 60
let bottomBorder:CGFloat = 50
let graphHeight = height - topBorder - bottomBorder
let maxValue = maxElement(graphPoints)
var columnYPoint = { (graphPoint:Int) -> CGFloat in
var y:CGFloat = CGFloat(graphPoint) /
CGFloat(maxValue) * graphHeight
y = graphHeight + topBorder - y // Flip the graph
return y
}

columnYPoint 同樣的也在一個閉包表達式中,將數組中得每一天作爲參數.返回縱座標的位置, 在0和最大飲水量之間.

因爲起始點爲左上角,但是你是從左下角開始繪製, columnYPoint會調整它的返回值來朝向你期望的地方。

繼續在drawRect(_:)的底部添加以下代碼:


// draw the line graph
 
UIColor.whiteColor().setFill()
UIColor.whiteColor().setStroke()
 
//set up the points line
var graphPath = UIBezierPath()
//go to start of line
graphPath.moveToPoint(CGPoint(x:columnXPoint(0),
y:columnYPoint(graphPoints[0])))
 
//add points for each item in the graphPoints array
//at the correct (x, y) for the point
for i in 1..<graphPoints.count {
let nextPoint = CGPoint(x:columnXPoint(i),
y:columnYPoint(graphPoints[i]))
graphPath.addLineToPoint(nextPoint)
}
 
graphPath.stroke()

在這個代碼快中,你創建了圖標的路徑, UIBezierPath用來連接graphPoints種的每一個點。

現在,storyboard中的圖標看起來是這樣的:

2-FirstGraphLine

現在你驗證了線已經正確繪製,從drawRect(_:)移除最後一行代碼


graphPath.stroke()

這行代碼讓你檢查線在storyboard是否被正確的繪製,點是否計算正確.

圖表漸變

現在在這個折線的路徑之下來繪製漸變效果.

首先設置裁剪路徑在drawRect(_:)的最下方:


//Create the clipping path for the graph gradient
 
//1 - save the state of the context (commented out for now)
//CGContextSaveGState(context)
 
//2 - make a copy of the path
var clippingPath = graphPath.copy() as! UIBezierPath
 
//3 - add lines to the copied path to complete the clip area
clippingPath.addLineToPoint(CGPoint(
x: columnXPoint(graphPoints.count - 1),
y:height))
clippingPath.addLineToPoint(CGPoint(
x:columnXPoint(0),
y:height))
clippingPath.closePath()
 
//4 - add the clipping path to the context
clippingPath.addClip()
 
//5 - check clipping path - temporary code
UIColor.greenColor().setFill()
let rectPath = UIBezierPath(rect: self.bounds)
rectPath.fill()
//end temporary code

通過上述代碼讓塊於塊之間分割:

  1. CGContextSaveGState 現在先不討論CGContextSaveGState,一會來說它是幹嘛的。
  2. 複製一個新的路徑來定義需要被填充的區域.
  3. 完成區域的角點並關閉路徑。這增加了右下和左下的點.
  4. 添加裁剪區域,當上下文被填充時,只有被裁減的區域會被填充。
  5. 填充上下文. 記住 rect就是上下文傳遞給drawRect(_:)被填充的區域。

現在storyboard中的圖標看起來應該是這樣的:

2-GraphClipping

下一步,你會將可愛的綠色換成你爲背景創建的漸變的效果

移除drawRect(_:)底部填充綠色的臨時代碼,換成以下代碼:


let highestYPoint = columnYPoint(maxValue)
startPoint = CGPoint(x:margin, y: highestYPoint)
endPoint = CGPoint(x:margin, y:self.bounds.height)
 
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0)
//CGContextRestoreGState(context)

在這裏,你將找到最大的數字作爲漸變的起點。

You can’t fill the whole rect the same way you did with the green
color. The gradient would fill from the top of the context instead of
from the top of the graph, and the desired gradient wouldn’t show up.

你不能填充這個rect就像填充綠色一樣,漸變會從上下問得頂部而不是圖標的頂部開始進行漸變。

注意CGContextRestoreState的註釋,你在繪製之後需要取消註釋。

drawRect(_:)之後添加代碼:


//draw the line on top of the clipped gradient
graphPath.lineWidth = 2.0
graphPath.stroke()

這裏繪製了原始的路徑

現在圖標看起來是這樣的:

2-SecondGraphLine

drawRect(_:)的底部添加:


//Draw the circles on top of graph stroke
for i in 0..<graphPoints.count {
var point = CGPoint(x:columnXPoint(i), y:columnYPoint(graphPoints[i]))
point.x -= 5.0/2
point.y -= 5.0/2
 
let circle = UIBezierPath(ovalInRect:
CGRect(origin: point,
size: CGSize(width: 5.0, height: 5.0)))
circle.fill()
}

繪製小圓點的代碼並不是新的。這裏將爲每一個數組中的元素的位置繪製一個圓點。

2-GraphWithFlatCircles

現在爲什麼不好看呢,對了,小圓點。沒事,我們一會加上去。

上下文狀態

圖形上下文可以保存狀態。當你設置了許多上下文屬性,如填充顏色,色域,變換矩陣,或裁剪區域,你實際上設置爲當前圖形狀態。

你可以使用CGContextSaveGState()保存狀態,這會就將當前狀態的拷貝壓進狀態棧。你可以改變上下文的屬性,但是當你調用CGContextRestoreGState()原來的狀態將會出棧,還原當前的狀態。

GraphView.swiftdrawRect(_:)方法中,注意CGContextSaveGState() 發生在創建裁剪路徑之前, CGContextRestoreGState()發生在裁剪路徑之後。
這裏我們做了一下幾點:
1. 通過CGContextSaveGState()將原始狀態壓入棧.
2. 添加裁剪路徑到新的狀態.
3. 在裁剪路徑中繪圖.
4. 通過CGContextRestoreGState()還原狀態,這是在添加裁剪路徑前的狀態。

現在的圖標和圓點看起來更加的清晰:

2-GraphWithCircles

drawRect(_:)後添加代碼來添加三條橫線:


//Draw horizontal graph lines on the top of everything
var linePath = UIBezierPath()
 
//top line
linePath.moveToPoint(CGPoint(x:margin, y: topBorder))
linePath.addLineToPoint(CGPoint(x: width - margin,
y:topBorder))
 
//center line
linePath.moveToPoint(CGPoint(x:margin,
y: graphHeight/2 + topBorder))
linePath.addLineToPoint(CGPoint(x:width - margin,
y:graphHeight/2 + topBorder))
 
//bottom line
linePath.moveToPoint(CGPoint(x:margin,
y:height - bottomBorder))
linePath.addLineToPoint(CGPoint(x:width - margin,
y:height - bottomBorder))
let color = UIColor(white: 1.0, alpha: 0.3)
color.setStroke()
 
linePath.lineWidth = 1.0
linePath.stroke()

沒有新的代碼,你需要做的只是移動到一個點然後水平畫線。

2-GraphWithAxisLines

添加圖標的標籤

現在你可以添加標籤來讓圖標的用戶體驗更好。
ViewController.swift添加 outlets 屬性:


//Label outlets
@IBOutlet weak var averageWaterDrunk: UILabel!
@IBOutlet weak var maxLabel: UILabel!

這兩個標籤將會動態的改變內容(飲水量的平均值和最大值)。

Main.storyboard 添加以下的 UILabels 作爲 Graph View的子視圖:

  1. “Water Drunk”
  2. “Average:”
  3. “2” (平均飲水量)
  4. “99” (最大飲水量). 右對齊
  5. “0”. 右對齊
  6. 每一天的標籤,居中對齊.

通過Shift全選所有標籤,改變字體爲Avenir Next Condensed, Medium style.

2-LabelledGraph

Main.storyboard關聯averageWaterDrunkmaxLabel .按住Control從View Controller拖分別拖到相應的標籤。

2-ConnectLabels

對於每天得標籤來說,在Attributes Inspector 改變 View’s Tag 屬性,分別食從1到7

2-LabelViewTag

現在已經完成了圖標的設置,在Main.storyboard 選擇 Graph View並選擇Hidden,讓程序第一次出現的時候不顯示圖標。

2-GraphHidden

ViewController.swift 添加以下方法來設置標籤:


 func setupGraphDisplay() {                                               
                                                                          
   //Use 7 days for graph - can use any number,                           
   //but labels and sample data are set up for 7 days                     
   let noOfDays:Int = 7                                                   
                                                                          
   //1 - replace last day with today's actual data                        
   graphView.graphPoints[graphView.graphPoints.count-1] = counterView.cou 
 nter                                                                     
                                                                          
   //2 - indicate that the graph needs to be redrawn                      
   graphView.setNeedsDisplay()                                            
                                                                          
   maxLabel.text = "\(maxElement(graphView.graphPoints))"                 
                                                                          
   //3 - calculate average from graphPoints                               
   let average = graphView.graphPoints.reduce(0, combine: +)              
             / graphView.graphPoints.count                                
   averageWaterDrunk.text = "\(average)"                                  
                                                                          
   //set up labels                                                        
   //day of week labels are set up in storyboard with tags                
   //today is last day of the array need to go backwards                  
                                                                          
   //4 - get today's day number                                           
   let dateFormatter = NSDateFormatter()                                  
   let calendar = NSCalendar.currentCalendar()                            
   let componentOptions:NSCalendarUnit = .CalendarUnitWeekday             
   let components = calendar.components(componentOptions,                 
                                        fromDate: NSDate())               
   var weekday = components.weekday                                       
                                                                          
   let days = ["S", "S", "M", "T", "W", "T", "F"]                         
                                                                          
   //5 - set up the day name labels with correct day                      
   for i in reverse(1...days.count) {                                     
     if let labelView = graphView.viewWithTag(i) as? UILabel {            
       if weekday == 7 {                                                  
         weekday = 0                                                      
       }                                                                  
       labelView.text = days[weekday--]                                   
       if weekday < 0 {                                                   
         weekday = days.count - 1                                         
       }                                                                  
     }                                                                    
   }                                                                      
 }

This looks a little burly, but it’s required to set up the calendar and retrieve the current day of the week. Take it in sections:

這看起來有點粗魯的,但它需要設置日曆和知道今天是星期幾:

  1. 你設置今天的數據將會作爲數組的最後一個元素插入. 在最後的項目中,Part
    3
    ,你可以擴展到60天的數據, 將會有一個方法來選取起止時間,但是這超出了本章的講解內容 :]
  2. 如果今天有變化,重繪數據.
  3. 這裏你使用 Swift的 reduce 來計算一週的平局飲水量; 這個在計算數組的總和的時候非常有用。
  4. 這一部分將當前的日期作爲iOS的日曆一週的結束.
  5. 這個循環從7到1,通過tag來獲取相應的視圖,這是正確的標題從days數組中。

還是在ViewController.swift,在counterViewTap(_:)方法的else分支中調用新方法來顯示圖表:

“`
setupGraphDisplay()

“`

運行程序,點擊計數器,萬歲,圖表將顯示所有的數據。

2-GraphFinished

掌握矩陣

現在程序看起來有點突兀,我們子啊第一部分創建的計數器還需要提升一下,在飲水量上添加指示。

2-Result

現在已經有CG方法繪圖的一些經驗了,你將會使用旋轉和平移。
可以看出這個標誌都是從中心輻射的:

2-LinesExpanded

和在上下文繪圖一樣,你可以通過旋轉,縮放,和平移來進行矩陣變幻。

一開始這可能是令你費解的,但是在經過一些練習之後,將會變得更有意義,變換的順序非常重要,我先說畫出你需要做的示意圖。
下面的示意圖就是通過旋轉然後在中間繪製矩形的結果。

2-RotatedContext

黑色的矩形食旋轉前的上下文。然後是綠的,再是紅得。有兩件事需要注意。

  1. 上下文根據左上角進行旋轉。
  2. 矩形依然繪製在中心在旋轉之後.

當你繪製計數器的標誌的時候,你需要先旋轉上下文,然後再繪製它。

2-RotatedTranslatedContext

在這個示意圖中,矩形的標記在左上方,藍色輪廓就是變換的上下文,然後上下文旋轉(紅色虛線),然後再次旋轉。

最後當紅色的標識被繪製完之後,它的角度如圖所示:

在上下文旋轉和平移得到紅色標記之後,它需要回到中心來進行下一次的變換得到綠的標誌。

就像你在裁剪的時候保存上下文一樣,你需要保存和恢復上下文狀態在每次矩陣變換的時候。

打開CounterView.swiftdrawRect(_:)的最後添加代碼來增加標記:

 //Counter View markers                                                   
                                                                          
 let context = UIGraphicsGetCurrentContext()                              
                                                                          
 //1 - save original state                                                
 CGContextSaveGState(context)                                             
 outlineColor.setFill()                                                   
                                                                          
 let markerWidth:CGFloat = 5.0                                            
 let markerSize:CGFloat = 10.0                                            
                                                                          
 //2 - the marker rectangle positioned at the top left                    
 var markerPath = UIBezierPath(rect:                                      
        CGRect(x: -markerWidth/2,                                         
        y: 0,                                                             
        width: markerWidth,                                               
        height: markerSize))                                              
                                                                          
 //3 - move top left of context to the previous center position           
 CGContextTranslateCTM(context,                                           
                       rect.width/2,                                      
                       rect.height/2)                                     
                                                                          
 for i in 1...NoOfGlasses {                                               
   //4 - save the centred context                                         
   CGContextSaveGState(context)                                           
                                                                          
   //5 - calculate the rotation angle                                     
   var angle = arcLengthPerGlass * CGFloat(i) + startAngle - π/2          
                                                                          
   //rotate and translate                                                 
   CGContextRotateCTM(context, angle)                                     
   CGContextTranslateCTM(context,                                         
                         0,                                               
                         rect.height/2 - markerSize)                      
                                                                          
   //6 - fill the marker rectangle                                        
   markerPath.fill()                                                      
                                                                          
   //7 - restore the centred context for the next rotate                  
   CGContextRestoreGState(context)                                        
 }                                                                        
                                                                          
 //8 - restore the original state in case of more painting                
 CGContextRestoreGState(context) 

這裏你做了下面幾點:

  1. 矩陣變換之前,保存上下文狀態.
  2. 得到位路勁的位置和形狀,但是現在還不繪製.
  3. 旋轉能夠圍繞原始的中心點(上面的藍色線)
  4. 對於每個標誌, 首先保存居中上下文狀態.
  5. 根據先前計算的值來進行旋轉和平移上下文.
  6. 在旋轉後的左上角繪製矩形並平移.
  7. 讀取居中上下文狀態
  8. 讀取最原始的上下文狀態.

喔,乾的不錯,現在來運行程序,欣賞這個漂亮的界面。
2-FinalPart2

接下來該做什麼?

這裏,
是到現在爲止爲止所有的代碼.

現在你已經學會了繪製路徑,漸變效果和通過上下文的矩陣變化

在第三部分的核心繪圖教程中,
你將創建圖案的背景和繪製矢量圖。

發佈了180 篇原創文章 · 獲贊 85 · 訪問量 156萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章