版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2018.10.21 星期日 |
前言
quartz
是一個(gè)通用的術(shù)語(yǔ),用于描述在iOS
和MAC OS X
中整個(gè)媒體層用到的多種技術(shù) 包括圖形赦颇、動(dòng)畫示惊、音頻、適配辞色。Quart 2D
是一組二維繪圖和渲染API
骨宠,Core Graphic
會(huì)使用到這組API
,Quartz Core
專指Core Animation
用到的動(dòng)畫相關(guān)的庫(kù)相满、API
和類层亿。CoreGraphics
是UIKit
下的主要繪圖系統(tǒng),頻繁的用于繪制自定義視圖雳灵。Core Graphics
是高度集成于UIView
和其他UIKit
部分的棕所。Core Graphics
數(shù)據(jù)結(jié)構(gòu)和函數(shù)可以通過(guò)前綴CG
來(lái)識(shí)別闸盔。在app中很多時(shí)候繪圖等操作我們要利用CoreGraphic
框架悯辙,它能繪制字符串、圖形迎吵、漸變色等等躲撰,是一個(gè)很強(qiáng)大的工具。感興趣的可以看我另外幾篇击费。
1. CoreGraphic框架解析(一)—— 基本概覽
2. CoreGraphic框架解析(二)—— 基本使用
3. CoreGraphic框架解析(三)—— 類波浪線的實(shí)現(xiàn)
4. CoreGraphic框架解析(四)—— 基本架構(gòu)補(bǔ)充
5. CoreGraphic框架解析 (五)—— 基于CoreGraphic的一個(gè)簡(jiǎn)單繪制示例 (一)
開始
在第二部分中拢蛋,您將深入研究Core Graphics,了解繪制漸變和使用CGContexts
進(jìn)行變換操作蔫巩。
你現(xiàn)在要離開UIKit的舒適世界谆棱,進(jìn)入Core Graphics的黑社會(huì)。
Apple的這張圖片從概念上描述了相關(guān)的框架:
UIKit是最頂層的圆仔,也是和開發(fā)者最容易接觸的的垃瞧。 您已經(jīng)使用了UIBezierPath
,它是Core Graphics
的CGPath
的UIKit包裝器坪郭。
Core Graphics框架基于Quartz
高級(jí)繪圖引擎个从。 它提供低級(jí),輕量級(jí)的2D渲染。 您可以使用此框架來(lái)處理基于路徑的繪圖嗦锐,轉(zhuǎn)換嫌松,顏色管理等等。
關(guān)于下層Core Graphics
對(duì)象和函數(shù)的一件事是它們總是有前綴CG
奕污,所以它們很容易識(shí)別萎羔。
當(dāng)你到本教程結(jié)束時(shí),你將創(chuàng)建一個(gè)如下所示的圖形視圖:
在繪制圖表視圖之前碳默,您將在故事板中進(jìn)行設(shè)置外驱,并創(chuàng)建動(dòng)畫轉(zhuǎn)換的代碼以顯示圖表視圖。
完整的視圖層次結(jié)構(gòu)如下所示:
前一篇我們已經(jīng)做到了下面這個(gè)程度腻窒。 唯一的區(qū)別是在Main.storyboard
中昵宇,CounterView
位于另一個(gè)視圖(帶黃色背景)內(nèi)。 構(gòu)建并運(yùn)行儿子,這將是您將看到的:
轉(zhuǎn)到File \ New \ File ...
瓦哎,選擇iOS \ Source \ Cocoa Touch Class
模板,然后單擊Next
柔逼。輸入名稱GraphView
作為類名蒋譬,選擇子類UIView并將語(yǔ)言設(shè)置為Swift。單擊Next
愉适,然后單擊Create
犯助。
現(xiàn)在,在Main.storyboard
中维咸,單擊Document Outline
中黃色視圖的名稱兩次以重命名剂买,然后將其命名為Container View
。將新UIView從對(duì)象庫(kù)拖動(dòng)到Counter View
下面的Container View
內(nèi)部癌蓖。
在Identity Inspector
中將新視圖的類更改為GraphView
瞬哼。剩下的唯一事情就是為新的GraphView添加約束,類似于在本教程前一部分中的操作:
- 選中
GraphView
后租副,按住Control鍵從中心稍微左側(cè)(仍在視圖中)進(jìn)行拖動(dòng)坐慰,然后從彈出菜單中選擇Width
。 - 同樣用僧,按住Control鍵在選中GraphView的情況下结胀,從中心稍微向上(仍然在視圖中)進(jìn)行控制 - 拖動(dòng),然后從彈出菜單中選擇
Height
责循。 - 按住Control鍵從視圖內(nèi)部向左拖動(dòng)到視圖外部糟港,然后選擇
Center Horizontally in Container
。 - 按住Control鍵從視圖內(nèi)部向上拖動(dòng)到視圖外部沼死,然后選擇
Center Vertically in Container
着逐。
在Size Inspector
中編輯約束常量以匹配以下內(nèi)容:
你的Document Outline
應(yīng)該像下面這樣
您需要容器視圖的原因是在Counter View
和Graph View
之間進(jìn)行動(dòng)畫過(guò)渡。
轉(zhuǎn)到ViewController.swift
并為Container
和Graph Views
添加屬性outlets
:
@IBOutlet weak var containerView: UIView!
@IBOutlet weak var graphView: GraphView!
這為容器視圖和圖形視圖創(chuàng)建了一個(gè)outlet
。 現(xiàn)在將它們連接到您在故事板中創(chuàng)建的視圖耸别。
返回Main.storyboard
并將Graph View
和Container View
連接到outlet
:
Seting up the Animated Transition - 設(shè)置動(dòng)畫轉(zhuǎn)場(chǎng)
仍然在Main.storyboard
中健芭,將Tap Gesture Recognizer
從對(duì)象庫(kù)拖到Document Outline
中的Container View
:
轉(zhuǎn)到ViewController.swift
并將此屬性添加到類的頂部:
var isGraphViewShowing = false
這只是標(biāo)記當(dāng)前是否顯示圖表視圖。
現(xiàn)在添加tap
方法來(lái)進(jìn)行轉(zhuǎn)場(chǎng):
@IBAction func counterViewTap(_ gesture: UITapGestureRecognizer?) {
if (isGraphViewShowing) {
//hide Graph
UIView.transition(from: graphView,
to: counterView,
duration: 1.0,
options: [.transitionFlipFromLeft, .showHideTransitionViews],
completion:nil)
} else {
//show Graph
UIView.transition(from: counterView,
to: graphView,
duration: 1.0,
options: [.transitionFlipFromRight, .showHideTransitionViews],
completion: nil)
}
isGraphViewShowing = !isGraphViewShowing
}
UIView.transition(from:to:duration:options:completion :)
執(zhí)行水平翻轉(zhuǎn)過(guò)渡秀姐。 其他過(guò)渡是交叉溶解慈迈,垂直翻轉(zhuǎn)和向上或向下卷曲。 轉(zhuǎn)換使用.showHideTransitionViews
常量省有,這意味著您不必刪除視圖以防止它在轉(zhuǎn)換中hidden
后顯示痒留。
在pushButtonPressed(_ :)
的末尾添加此代碼:
if isGraphViewShowing {
counterViewTap(nil)
}
如果用戶在顯示圖表時(shí)按下加號(hào)按鈕,顯示屏將向后擺動(dòng)以顯示計(jì)數(shù)器蠢沿。
最后伸头,要使此轉(zhuǎn)換工作,請(qǐng)返回Main.storyboard
并將您的點(diǎn)擊手勢(shì)連接到新添加的counterViewTap(gesture:)
方法:
構(gòu)建并運(yùn)行應(yīng)用程序舷蟀。 目前恤磷,您在啟動(dòng)應(yīng)用時(shí)會(huì)看到圖表視圖。 稍后野宜,您將隱藏圖表視圖扫步,因此計(jì)數(shù)器視圖將首先出現(xiàn)。 點(diǎn)按它匈子,您將看到轉(zhuǎn)換翻轉(zhuǎn)河胎。
Analysis of the Graph View - 圖表分析
還記得第1部分中的Painter’s Model
嗎? 它解釋了使用Core Graphics繪圖是從圖像背面到前面完成的虎敦,因此在編碼之前需要記住順序游岳。 對(duì)于Flo
的圖,那將是:
- 漸變背景視圖
- 圖下的剪裁漸變
- 圖線
- 圖表的圓圈指向
- 水平圖線
- 圖表標(biāo)簽
Drawing a Gradient - 繪制梯度
您現(xiàn)在將在圖表視圖中繪制漸變原茅。
轉(zhuǎn)到GraphView.swift
并將代碼替換為:
import UIKit
@IBDesignable class GraphView: UIView {
// 1
@IBInspectable var startColor: UIColor = .red
@IBInspectable var endColor: UIColor = .green
override func draw(_ rect: CGRect) {
// 2
let context = UIGraphicsGetCurrentContext()!
let colors = [startColor.cgColor, endColor.cgColor]
// 3
let colorSpace = CGColorSpaceCreateDeviceRGB()
// 4
let colorLocations: [CGFloat] = [0.0, 1.0]
// 5
let gradient = CGGradient(colorsSpace: colorSpace,
colors: colors as CFArray,
locations: colorLocations)!
// 6
let startPoint = CGPoint.zero
let endPoint = CGPoint(x: 0, y: bounds.height)
context.drawLinearGradient(gradient,
start: startPoint,
end: endPoint,
options: [])
}
}
這里有幾件事要做:
- 1) 您可以將漸變的開始和結(jié)束顏色設(shè)置為
@IBInspectable
屬性吭历,以便您可以在故事板中更改它們。 - 2)
CG
繪圖函數(shù)需要知道它們將繪制的上下文擂橘,因此您使用UIKit方法UIGraphicsGetCurrentContext()
來(lái)獲取當(dāng)前上下文。這是draw(_:)
繪制的地方摩骨。 - 3) 所有上下文都有顏色空間通贞。這可能是
CMYK
或灰度,但在這里你使用的是RGB
色彩空間恼五。 - 4) 顏色停止描述漸變中的顏色變換的位置昌罩。在這個(gè)例子中,你只有兩種顏色灾馒,紅色變?yōu)榫G色茎用,但你可以有一個(gè)三個(gè)數(shù)的數(shù)組,并且紅色變?yōu)樗{(lán)色變?yōu)榫G色。停止點(diǎn)位于0和1之間轨功,其中0.33是通過(guò)漸變的三分之一旭斥。
- 5) 創(chuàng)建實(shí)際漸變,定義顏色空間古涧,顏色和顏色停止點(diǎn)垂券。
- 6) 最后,繪制漸變羡滑。
CGContextDrawLinearGradient()
采用以下參數(shù):- 要繪制的
CGContext
-
CGGradient
具有色彩空間菇爪,顏色和停止 - 起點(diǎn)
- 終點(diǎn)
- 用于擴(kuò)展?jié)u變的選項(xiàng)標(biāo)志
- 要繪制的
漸變將填充整個(gè)draw(_:)
的rect
。
設(shè)置Xcode柒昏,以便使用Assistant Editor (Show Assistant Editor…\Counterparts\Main.storyboard)
對(duì)您的代碼和故事板進(jìn)行并排查看凳宙,您將看到漸變顯示在圖表視圖上。
在故事板中职祷,選擇Graph View
近速。 然后在Attributes Inspector
中,將Start Color
更改為RGB(250,233,222)
堪旧,將End Color
更改為RGB(252,79,8)
(單擊顏色削葱,然后單擊Other\Color Sliders
):
現(xiàn)在做一些清理工作。 在Main.storyboard
中淳梦,依次選擇每個(gè)視圖(ViewController主視圖除外)析砸,并將Background Color
設(shè)置為Clear Color
。 您不再需要黃色爆袍,按鈕視圖也應(yīng)該具有透明背景首繁。
構(gòu)建并運(yùn)行應(yīng)用程序,您會(huì)發(fā)現(xiàn)圖形看起來(lái)更好陨囊,或者至少是它的背景弦疮。
Clipping Areas - 剪裁區(qū)域
剛剛使用漸變時(shí),您填充了整個(gè)視圖的上下文區(qū)域蜘醋。 但是胁塞,您可以創(chuàng)建用作剪切區(qū)域的路徑,而不是用于繪制压语。 剪切區(qū)域允許您定義要填充的區(qū)域啸罢,而不是整個(gè)上下文。
轉(zhuǎn)到GraphView.swift
胎食。
首先扰才,在GraphView
的頂部添加這些常量,我們稍后將使用它們進(jìn)行繪制:
private struct Constants {
static let cornerRadiusSize = CGSize(width: 8.0, height: 8.0)
static let margin: CGFloat = 20.0
static let topBorder: CGFloat = 60
static let bottomBorder: CGFloat = 50
static let colorAlpha: CGFloat = 0.3
static let circleDiameter: CGFloat = 5.0
}
在draw(_:)
的頂部添加下面代碼
let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: .allCorners,
cornerRadii: Constants.cornerRadiusSize)
path.addClip()
這將創(chuàng)建一個(gè)約束漸變的剪切區(qū)域厕怜。 您將很快使用相同的技巧在圖線下繪制第二個(gè)漸變衩匣。
構(gòu)建并運(yùn)行應(yīng)用程序蕾总,看看你的圖表視圖有漂亮的圓角:
注意:使用Core Graphics繪制靜態(tài)視圖通常足夠快,但如果您的視圖移動(dòng)或需要頻繁重繪琅捏,則應(yīng)使用
Core Animation
層生百。Core Animation
經(jīng)過(guò)優(yōu)化,因此GPU(而不是CPU)可以處理大部分處理午绳。相反置侍,CPU處理Core Graphics
在draw(_ :)
中執(zhí)行的視圖繪制。您可以使用CALayer的
cornerRadius
屬性創(chuàng)建圓角拦焚,而不是使用剪切路徑蜡坊,但您應(yīng)該針對(duì)您的情況進(jìn)行優(yōu)化。
Tricky Calculations for Graph Points - 圖形點(diǎn)的棘手計(jì)算
現(xiàn)在赎败,您將從繪圖中稍作休息來(lái)制作圖表秕衙。你會(huì)繪制7個(gè)點(diǎn),x軸將是“星期幾”僵刮,y軸將是“喝的杯水的數(shù)量”据忘。
首先,設(shè)置本周的樣本數(shù)據(jù)搞糕。
仍然在GraphView.swift
中勇吊,在類的頂部添加以下屬性:
//Weekly sample data
var graphPoints = [4, 2, 6, 4, 5, 8, 3]
這包含代表七天的樣本數(shù)據(jù)。 忽略關(guān)于將其更改為let
值的警告窍仰,因?yàn)樯院笪覀冃枰獙⑵渥鳛?code>var汉规。
將此代碼添加到draw(_:)
的頂部
let width = rect.width
let height = rect.height
并將此代碼添加到draw(_:)
結(jié)束
//calculate the x point
let margin = Constants.margin
let graphWidth = width - margin * 2 - 4
let columnXPoint = { (column: Int) -> CGFloat in
//Calculate the gap between points
let spacing = graphWidth / CGFloat(self.graphPoints.count - 1)
return CGFloat(column) * spacing + margin + 2
}
x軸點(diǎn)由7個(gè)等間距點(diǎn)組成。 上面的代碼是一個(gè)閉包表達(dá)式驹吮。 它可以作為函數(shù)添加针史,但對(duì)于像這樣的小型計(jì)算,您可以將它們保持內(nèi)聯(lián)碟狞。
columnXPoint
將列作為參數(shù)啄枕,并返回一個(gè)值,其中該點(diǎn)應(yīng)位于x軸上族沃。
添加代碼來(lái)計(jì)算draw(_:)
結(jié)束時(shí)的y軸點(diǎn):
// calculate the y point
let topBorder = Constants.topBorder
let bottomBorder = Constants.bottomBorder
let graphHeight = height - topBorder - bottomBorder
let maxValue = graphPoints.max()!
let columnYPoint = { (graphPoint: Int) -> CGFloat in
let y = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight
return graphHeight + topBorder - y // Flip the graph
}
columnYPoint
也是一個(gè)閉包表達(dá)式频祝,它將星期幾數(shù)組中的值作為參數(shù)。 它返回y
位置竭业,介于0和最大杯水?dāng)?shù)之間智润。
由于Core Graphics中的原點(diǎn)位于左上角,并且您從左下角的原點(diǎn)繪制圖形未辆,因此columnYPoint
會(huì)調(diào)整其返回值,以使圖形朝向您期望的方向锯玛。
繼續(xù)在draw(_:)
結(jié)束時(shí)添加線條繪圖代碼
// draw the line graph
UIColor.white.setFill()
UIColor.white.setStroke()
// set up the points line
let graphPath = UIBezierPath()
// go to start of line
graphPath.move(to: 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.addLine(to: nextPoint)
}
graphPath.stroke()
在此塊中咐柜,您將為圖形創(chuàng)建路徑兼蜈。 UIBezierPath
是從graphPoints
中每個(gè)元素的x和y點(diǎn)構(gòu)建的。
故事板中的圖表視圖現(xiàn)在應(yīng)如下所示:
既然您已經(jīng)驗(yàn)證了線條的正確繪制拙友,請(qǐng)從draw(_:)
結(jié)束時(shí)刪除它
graphPath.stroke()
這只是為了您可以查看故事板中的行并驗(yàn)證計(jì)算是否正確为狸。
A Gradient Graph - 梯度圖
現(xiàn)在,您將使用路徑作為剪切路徑在此路徑下創(chuàng)建漸變遗契。
首先在draw(_:)
結(jié)束時(shí)設(shè)置剪切路徑:
//Create the clipping path for the graph gradient
//1 - save the state of the context (commented out for now)
//context.saveGState()
//2 - make a copy of the path
let clippingPath = graphPath.copy() as! UIBezierPath
//3 - add lines to the copied path to complete the clip area
clippingPath.addLine(to: CGPoint(x: columnXPoint(graphPoints.count - 1), y:height))
clippingPath.addLine(to: CGPoint(x:columnXPoint(0), y:height))
clippingPath.close()
//4 - add the clipping path to the context
clippingPath.addClip()
//5 - check clipping path - temporary code
UIColor.green.setFill()
let rectPath = UIBezierPath(rect: rect)
rectPath.fill()
//end temporary code
上述代碼的逐節(jié)細(xì)分:
- 1)
context.saveGState()
暫時(shí)被注釋掉了 - 一旦你理解了它的作用辐棒,你馬上就會(huì)回到這一點(diǎn)。 - 2) 將繪制的路徑復(fù)制到新路徑牍蜂,該路徑定義要用漸變填充的區(qū)域漾根。
- 3) 完成帶角點(diǎn)的區(qū)域并關(guān)閉路徑。 這會(huì)添加圖表的右下角和左下角鲫竞。
- 4) 將剪切路徑添加到上下文辐怕。 填充上下文時(shí),實(shí)際只填充剪切的路徑从绘。
- 5) 填充上下文寄疏。 請(qǐng)記住,
rect
是傳遞給draw(_ :)
的上下文區(qū)域僵井。
故事板中的圖表視圖現(xiàn)在應(yīng)如下所示:
接下來(lái)陕截,您將使用從用于背景漸變的顏色創(chuàng)建的漸變替換可愛的綠色。
從draw(_:)
結(jié)束中刪除帶有綠色填充的臨時(shí)代碼批什,然后添加以下代碼:
let highestYPoint = columnYPoint(maxValue)
let graphStartPoint = CGPoint(x: margin, y: highestYPoint)
let graphEndPoint = CGPoint(x: margin, y: bounds.height)
context.drawLinearGradient(gradient, start: graphStartPoint, end: graphEndPoint, options: [])
//context.restoreGState()
在這個(gè)區(qū)塊中农曲,您會(huì)發(fā)現(xiàn)杯水的最大數(shù)量,并將其作為漸變的起點(diǎn)渊季。
您無(wú)法像使用綠色一樣填充整個(gè)rect
朋蔫。 漸變將從上下文的頂部而不是從圖的頂部填充,并且不會(huì)顯示所需的漸變却汉。
記下注釋掉的context.restoreGState()
- 在繪制繪圖點(diǎn)的圓圈后驯妄,您將刪除注釋。
在draw(_:)
結(jié)束時(shí)添加以下內(nèi)容:
//draw the line on top of the clipped gradient
graphPath.lineWidth = 2.0
graphPath.stroke()
此代碼繪制原始路徑合砂。
你的圖表現(xiàn)在正在形成:
Drawing the Data Points - 繪制數(shù)據(jù)點(diǎn)
在draw(_:)
結(jié)束時(shí)青扔,添加以下內(nèi)容:
//Draw the circles on top of the graph stroke
for i in 0..<graphPoints.count {
var point = CGPoint(x: columnXPoint(i), y: columnYPoint(graphPoints[i]))
point.x -= Constants.circleDiameter / 2
point.y -= Constants.circleDiameter / 2
let circle = UIBezierPath(ovalIn: CGRect(origin: point, size: CGSize(width: Constants.circleDiameter, height: Constants.circleDiameter)))
circle.fill()
}
此代碼繪制繪圖點(diǎn)并不是什么新鮮事。 它在計(jì)算的x和y點(diǎn)處為數(shù)組中的每個(gè)元素填充圓形路徑翩伪。
嗯......但是故事板上出現(xiàn)的不是很好的圓形圓點(diǎn)微猖!這是怎么回事?
Context States - 上下文狀態(tài)
圖形上下文可以保存狀態(tài)缘屹。設(shè)置許多上下文屬性(如填充顏色凛剥,變換矩陣,顏色空間或剪輯區(qū)域(fill color, transformation matrix, color space or clip region)
)時(shí)轻姿,實(shí)際上是為當(dāng)前圖形狀態(tài)設(shè)置它們犁珠。
您可以使用context.saveGState()
來(lái)保存狀態(tài)逻炊,它將當(dāng)前圖形狀態(tài)的副本推送到狀態(tài)堆棧。您還可以更改上下文屬性犁享,但是當(dāng)您調(diào)用context.restoreGState()
時(shí)余素,原始狀態(tài)將從堆棧中取出,并且上下文屬性將還原炊昆。這就是為什么你看到了你的點(diǎn)的奇怪問(wèn)題桨吊。
仍然在GraphView.swift
中,在draw(_ :)
中凤巨,取消注釋在創(chuàng)建剪切路徑之前發(fā)生的context.saveGState()
视乐,并取消注釋在使用剪切路徑之后發(fā)生的context.restoreGState()
。
通過(guò)這樣做磅甩,你:
- 1) 使用
context.saveGState()
將原始圖形狀態(tài)推送到堆棧炊林。 - 2) 將剪切路徑添加到新的圖形狀態(tài)。
- 3) 在剪切路徑中繪制漸變卷要。
- 4) 使用
context.restoreGState()
恢復(fù)原始圖形狀態(tài)渣聚。這是您添加剪切路徑之前的狀態(tài)。
你的圖形線和圓圈現(xiàn)在應(yīng)該更加清晰:
在draw(_:)
結(jié)束時(shí)僧叉,添加代碼以繪制三條水平線:
//Draw horizontal graph lines on the top of everything
let linePath = UIBezierPath()
//top line
linePath.move(to: CGPoint(x: margin, y: topBorder))
linePath.addLine(to: CGPoint(x: width - margin, y: topBorder))
//center line
linePath.move(to: CGPoint(x: margin, y: graphHeight/2 + topBorder))
linePath.addLine(to: CGPoint(x: width - margin, y: graphHeight/2 + topBorder))
//bottom line
linePath.move(to: CGPoint(x: margin, y:height - bottomBorder))
linePath.addLine(to: CGPoint(x: width - margin, y: height - bottomBorder))
let color = UIColor(white: 1.0, alpha: Constants.colorAlpha)
color.setStroke()
linePath.lineWidth = 1.0
linePath.stroke()
此代碼中沒有任何內(nèi)容是新的奕枝。 你所做的只是移動(dòng)到一個(gè)點(diǎn)并繪制一條水平線。
Adding the Graph Labels - 添加圖形標(biāo)簽
現(xiàn)在瓶堕,您將添加標(biāo)簽以使圖形用戶友好隘道。
轉(zhuǎn)到ViewController.swift
并添加這些outlet
屬性:
//Label outlets
@IBOutlet weak var averageWaterDrunk: UILabel!
@IBOutlet weak var maxLabel: UILabel!
@IBOutlet weak var stackView: UIStackView!
這會(huì)為您想要?jiǎng)討B(tài)更改文本的兩個(gè)標(biāo)簽(平均水量標(biāo)簽,最大水量標(biāo)簽)以及帶有日期名稱標(biāo)簽的StackView
添加outlets
郎笆。
現(xiàn)在轉(zhuǎn)到Main.storyboard
并將以下視圖添加為圖表視圖的子視圖:
- 1)
UILabel
文字“Water Drunk”
- 2)
UILabel
谭梗,文字Average
- 3)
UILabel
,文本“2”
宛蚓,旁邊是Average
標(biāo)簽 - 4)
UILabel
激捏,文本“99”
,右側(cè)對(duì)齊圖形頂部 - 5)
UILabel
凄吏,文本“0”
远舅,右對(duì)齊到圖形的底部 - 6) 一個(gè)水平StackView,每周的每一天都有標(biāo)簽 - 每個(gè)文本的代碼都會(huì)更改痕钢。 中心對(duì)齊图柏。
按住Shift鍵選擇所有標(biāo)簽,然后將字體更改為Avenir Next Condensed, Medium style
任连。
將averageWaterDrunk
蚤吹,maxLabel
和stackView
連接到Main.storyboard
中的相應(yīng)視圖。 按住Control鍵從View Controller
拖動(dòng)到正確的標(biāo)簽随抠,然后從彈出窗口中選擇outlet
:
現(xiàn)在您已完成圖形視圖的設(shè)置距辆,在Main.storyboard
中選擇Graph View
并選中Hidden
余佃,以便在應(yīng)用程序首次運(yùn)行時(shí)不顯示圖形暮刃。
轉(zhuǎn)到ViewController.swift
并添加此方法以設(shè)置標(biāo)簽:
func setupGraphDisplay() {
let maxDayIndex = stackView.arrangedSubviews.count - 1
// 1 - replace last day with today's actual data
graphView.graphPoints[graphView.graphPoints.count - 1] = counterView.counter
//2 - indicate that the graph needs to be redrawn
graphView.setNeedsDisplay()
maxLabel.text = "\(graphView.graphPoints.max()!)"
// 3 - calculate average from graphPoints
let average = graphView.graphPoints.reduce(0, +) / graphView.graphPoints.count
averageWaterDrunk.text = "\(average)"
// 4 - setup date formatter and calendar
let today = Date()
let calendar = Calendar.current
let formatter = DateFormatter()
formatter.setLocalizedDateFormatFromTemplate("EEEEE")
// 5 - set up the day name labels with correct days
for i in 0...maxDayIndex {
if let date = calendar.date(byAdding: .day, value: -i, to: today),
let label = stackView.arrangedSubviews[maxDayIndex - i] as? UILabel {
label.text = formatter.string(from: date)
}
}
}
這看起來(lái)有點(diǎn)繁瑣跨算,但需要設(shè)置日歷并檢索一周中的當(dāng)前日期:
- 1) 您將今天的數(shù)據(jù)設(shè)置為圖形數(shù)據(jù)數(shù)組中的最后一項(xiàng)。在最終項(xiàng)目中椭懊,您將通過(guò)將其替換為60天的樣本數(shù)據(jù)來(lái)擴(kuò)展它诸蚕,并且您將包含一個(gè)方法,該方法可以分割出最后x天的數(shù)據(jù)氧猬。數(shù)組背犯,但這超出了本次會(huì)議的范圍。
- 2) 如果今天的數(shù)據(jù)有任何變化盅抚,請(qǐng)重新繪制圖表漠魏。
- 3) 在這里你使用Swift的
reduce
來(lái)計(jì)算本周喝的杯水量,這是一個(gè)非常有用的方法來(lái)總結(jié)數(shù)組中的所有元素妄均。 - 4) 此部分以一種方式設(shè)置
DateFormatter
柱锹,它將獲得一天名稱的第一個(gè)字母。 - 5) 這個(gè)循環(huán)遍歷
stackView
中的所有標(biāo)簽丰包,我們?yōu)槿掌诟袷交绦蛑械拿總€(gè)標(biāo)簽設(shè)置文本禁熏。
仍然在ViewController.swift
中,從counterViewTap(_ :)
調(diào)用這個(gè)新方法邑彪。在條件的else
部分瞧毙,注釋顯示show graph
,添加以下代碼:
setupGraphDisplay()
運(yùn)行該應(yīng)用程序寄症,然后單擊計(jì)數(shù)器宙彪,查看效果:
Mastering the Matrix - 掌握矩陣
你的應(yīng)用看起來(lái)非常好! 您在第一部分中創(chuàng)建的計(jì)數(shù)器視圖可以進(jìn)行改進(jìn)有巧,例如添加標(biāo)記以指示每個(gè)要喝的杯水:
現(xiàn)在您已經(jīng)對(duì)CG函數(shù)進(jìn)行了一些實(shí)踐释漆,您將使用它們來(lái)旋轉(zhuǎn)和轉(zhuǎn)換繪圖上下文。
請(qǐng)注意剪决,這些標(biāo)記從中心輻射:
除了繪制上下文之外灵汪,您還可以選擇通過(guò)旋轉(zhuǎn),縮放和轉(zhuǎn)換上下文的變換矩陣來(lái)操縱上下文柑潦。
起初享言,這看起來(lái)很令人困惑,但在你完成這些練習(xí)后渗鬼,它會(huì)更有意義览露。 變換的順序很重要,因此首先我將概述您將使用圖表做什么譬胎。
下圖是旋轉(zhuǎn)上下文然后在上下文中心繪制一個(gè)矩形的結(jié)果差牛。
在旋轉(zhuǎn)上下文之前繪制黑色矩形命锄,然后是綠色矩形,然后是紅色矩形偏化。 有兩點(diǎn)需要注意:
- 1) 上下文在左上角旋轉(zhuǎn)(0,0)
- 2) 矩形仍在上下文的中心繪制脐恩,但在上下文旋轉(zhuǎn)后。
當(dāng)您繪制計(jì)數(shù)器視圖的標(biāo)記時(shí)侦讨,您將首先變換上下文驶冒,然后旋轉(zhuǎn)它。
在此圖中韵卤,矩形標(biāo)記位于上下文的最左上角骗污。 藍(lán)線勾勒出變換的上下文,然后上下文旋轉(zhuǎn)(紅色虛線)并再次變換沈条。
當(dāng)紅色矩形標(biāo)記最終被繪制到上下文中時(shí)需忿,它將以一定角度出現(xiàn)在視圖中。
旋轉(zhuǎn)上下文并平移以繪制紅色標(biāo)記后蜡歹,需要將其重置為中心屋厘,以便可以旋轉(zhuǎn)上下文并再次平移以繪制綠色標(biāo)記。
就像在Graph View
中使用剪切路徑保存上下文狀態(tài)一樣季稳,每次繪制標(biāo)記時(shí)擅这,都將使用變換矩陣保存和恢復(fù)狀態(tài)。
轉(zhuǎn)到CounterView.swift
并將此代碼添加到draw(_:)
結(jié)束以將標(biāo)記添加到計(jì)數(shù)器:
//Counter View markers
let context = UIGraphicsGetCurrentContext()!
//1 - save original state
context.saveGState()
outlineColor.setFill()
let markerWidth: CGFloat = 5.0
let markerSize: CGFloat = 10.0
//2 - the marker rectangle positioned at the top left
let markerPath = UIBezierPath(rect: CGRect(x: -markerWidth / 2, y: 0, width: markerWidth, height: markerSize))
//3 - move top left of context to the previous center position
context.translateBy(x: rect.width / 2, y: rect.height / 2)
for i in 1...Constants.numberOfGlasses {
//4 - save the centred context
context.saveGState()
//5 - calculate the rotation angle
let angle = arcLengthPerGlass * CGFloat(i) + startAngle - .pi / 2
//rotate and translate
context.rotate(by: angle)
context.translateBy(x: 0, y: rect.height / 2 - markerSize)
//6 - fill the marker rectangle
markerPath.fill()
//7 - restore the centred context for the next rotate
context.restoreGState()
}
//8 - restore the original state in case of more painting
context.restoreGState()
這就是你剛才所做的:
- 1) 在操作上下文的矩陣之前景鼠,您可以保存矩陣的原始狀態(tài)仲翎。
- 2) 定義路徑的位置和形狀 - 但您還沒有繪制它。
- 3) 移動(dòng)上下文铛漓,以便在上下文的原始中心周圍進(jìn)行旋轉(zhuǎn)溯香。 (上圖中的藍(lán)線。)
- 4) 對(duì)于每個(gè)標(biāo)記浓恶,首先保存居中的上下文狀態(tài)玫坛。
- 5) 使用先前計(jì)算的單個(gè)角度,確定每個(gè)標(biāo)記的角度并旋轉(zhuǎn)和轉(zhuǎn)換上下文包晰。
- 6) 在旋轉(zhuǎn)和變換的上下文的左上角繪制標(biāo)記矩形湿镀。
- 7) 恢復(fù)居中上下文的狀態(tài)。
- 8) 恢復(fù)沒有旋轉(zhuǎn)或變換的上下文的原始狀態(tài)伐憾。
現(xiàn)在構(gòu)建并運(yùn)行應(yīng)用程序勉痴,并欣賞Flo
的美麗且信息豐富的UI:
后記
本篇主要講述了基于CoreGraphic的一個(gè)簡(jiǎn)單繪制示例,感興趣的給個(gè)贊或者關(guān)注~~~