CoreGraphic框架解析 (七)—— 基于CoreGraphic的一個(gè)簡(jiǎn)單繪制示例 (三)

版本記錄

版本號(hào) 時(shí)間
V1.0 2018.10.21 星期日

前言

quartz是一個(gè)通用的術(shù)語(yǔ),用于描述在iOSMAC OS X 中整個(gè)媒體層用到的多種技術(shù) 包括圖形、動(dòng)畫(huà)、音頻、適配。Quart 2D 是一組二維繪圖和渲染APICore Graphic會(huì)使用到這組API,Quartz Core專指Core Animation用到的動(dòng)畫(huà)相關(guān)的庫(kù)、API和類。CoreGraphicsUIKit下的主要繪圖系統(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)單繪制示例 (一)
6. CoreGraphic框架解析 (六)—— 基于CoreGraphic的一個(gè)簡(jiǎn)單繪制示例 (二)

開(kāi)始

在我們的Core Graphics教程的第三部分也是最后一部分中,您將把Flo帶到最終形式。 具體來(lái)說(shuō),你將:

  • 為背景創(chuàng)建重復(fù)模式。
  • 從頭到尾畫(huà)一枚獎(jiǎng)牌,獎(jiǎng)勵(lì)用戶每天成功飲用八杯水。

您在本節(jié)中的任務(wù)是使用UIKit的pattern方法來(lái)創(chuàng)建此背景模式:

轉(zhuǎn)到File \ New \ File ...并選擇iOS iOS \ Source \ Cocoa Touch Class模板以創(chuàng)建一個(gè)名為BackgroundView的類,其為UIView子類。 單擊Next,然后單擊Create。

轉(zhuǎn)到Main.storyboard,選擇ViewController的主視圖,并在Identity Inspector中將類更改為BackgroundView。

使用Assistant Editor設(shè)置BackgroundView.swiftMain.storyboard,使它們并排放置。

BackgroundView.swift中的代碼替換為:

import UIKit

@IBDesignable
class BackgroundView: UIView {
  
  //1
  @IBInspectable var lightColor: UIColor = UIColor.orange
  @IBInspectable var darkColor: UIColor = UIColor.yellow
  @IBInspectable var patternSize: CGFloat = 200
  
  override func draw(_ rect: CGRect) {
    //2
    let context = UIGraphicsGetCurrentContext()!
    
    //3
    context.setFillColor(darkColor.cgColor)
    
    //4
    context.fill(rect)
  }
}

您的故事板的背景視圖現(xiàn)在應(yīng)該是黃色的。 以上代碼的更多細(xì)節(jié):

  • 1) lightColordarkColor具有@IBInspectable屬性,因此以后更容易配置背景顏色。 你使用橙色和黃色作為臨時(shí)顏色,這樣你就可以看到發(fā)生了什么。 patternSize控制重復(fù)模式的大小。 它最初設(shè)置為大型,因此很容易看出發(fā)生了什么。
  • 2) UIGraphicsGetCurrentContext()為您提供視圖的上下文,也是draw(_ rect :)繪制的地方。
  • 3) 使用Core Graphics方法setFillColor()設(shè)置上下文的當(dāng)前填充顏色。 請(qǐng)注意,在使用Core Graphics時(shí),您需要使用CGColor,這是darkColor的一個(gè)屬性。
  • 4) fill()不是設(shè)置矩形路徑,而是使用當(dāng)前填充顏色填充整個(gè)上下文。

您現(xiàn)在要使用UIBezierPath()繪制這三個(gè)橙色三角形。 這些數(shù)字對(duì)應(yīng)于以下代碼中的點(diǎn):

仍然在BackgroundView.swift中,將此代碼添加到draw(_ rect:)結(jié)束:

   
let drawSize = CGSize(width: patternSize, height: patternSize)
    
//insert code here
        
let trianglePath = UIBezierPath()
//1
trianglePath.move(to: CGPoint(x: drawSize.width/2, y: 0))
//2
trianglePath.addLine(to: CGPoint(x: 0, y: drawSize.height/2))
//3
trianglePath.addLine(to: CGPoint(x: drawSize.width, y: drawSize.height/2))
    
//4
trianglePath.move(to: CGPoint(x: 0,y: drawSize.height/2))
//5
trianglePath.addLine(to: CGPoint(x: drawSize.width/2, y: drawSize.height))
//6
trianglePath.addLine(to: CGPoint(x: 0, y: drawSize.height))
    
//7
trianglePath.move(to: CGPoint(x: drawSize.width, y: drawSize.height/2))
//8
trianglePath.addLine(to: CGPoint(x: drawSize.width/2, y: drawSize.height))
//9
trianglePath.addLine(to: CGPoint(x: drawSize.width, y: drawSize.height))
    
lightColor.setFill()
trianglePath.fill()

請(qǐng)注意您如何使用一條路徑繪制三個(gè)三角形。move(to:)就像在繪圖并將其移動(dòng)到新位置時(shí)從紙上抬起筆。

您的故事板現(xiàn)在應(yīng)該在背景視圖的左上角有一個(gè)橙色和黃色的圖像。

到目前為止,您已直接繪制到視圖的繪圖上下文中。 為了能夠重復(fù)此圖案(pattern),您需要在上下文之外創(chuàng)建一個(gè)圖像,然后將該圖像用作上下文中的模式。

找到以下內(nèi)容。 它接近于draw(_ rect:)的頂部,但是在初始上下文調(diào)用之后:

let drawSize = CGSize(width: patternSize, height: patternSize)

添加以下代碼,方便地在此處插入代碼:

UIGraphicsBeginImageContextWithOptions(drawSize, true, 0.0)
let drawingContext = UIGraphicsGetCurrentContext()!
    
//set the fill color for the new context
darkColor.setFill()
drawingContext.fill(CGRect(x: 0, y: 0, width: drawSize.width, height: drawSize.height))

嘿! 那些橙色三角形從故事板上消失了。 他們?nèi)ツ膬毫耍?/p>

UIGraphicsBeginImageContextWithOptions()創(chuàng)建一個(gè)新的上下文并將其設(shè)置為當(dāng)前繪圖上下文,因此您現(xiàn)在正在繪制這個(gè)新的上下文。 該方法的參數(shù)是:

  • 上下文的大小。
  • 上下文是否不透明 - 如果您需要透明度,那么這需要是錯(cuò)誤的。
  • 上下文的scale。 如果您正在使用視網(wǎng)膜屏幕,則應(yīng)為2.0,如果是iPhone 6 Plus,則應(yīng)為3.0。 但是,這使用0.0,這可確保自動(dòng)應(yīng)用設(shè)備的正確比例。

然后,您使用UIGraphicsGetCurrentContext()來(lái)獲取對(duì)此新上下文的引用。

然后用黃色填充新的上下文。 您可以通過(guò)將上下文不透明度設(shè)置為false來(lái)讓原始背景顯示,但繪制不透明上下文比繪制透明更快,并且該參數(shù)足以變?yōu)椴煌该鳌?/p>

將此代碼添加到draw(_ rect:)結(jié)束:

let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()

這從當(dāng)前上下文中提取UIImage。 當(dāng)您使用UIGraphicsEndImageContext()結(jié)束當(dāng)前上下文時(shí),繪圖上下文將恢復(fù)為視圖的上下文,因此draw(_ rect:)中的任何進(jìn)一步繪圖都會(huì)出現(xiàn)在視圖中。

要將圖像繪制為重復(fù)模式,請(qǐng)將此代碼添加到draw(_ rect:)結(jié)束:

UIColor(patternImage: image).setFill()
context.fill(rect)

這通過(guò)將圖像用作顏色而不是純色來(lái)創(chuàng)建新的UIColor

構(gòu)建并運(yùn)行應(yīng)用程序。 您現(xiàn)在應(yīng)該擁有相當(dāng)明亮的應(yīng)用背景。

轉(zhuǎn)到Main.storyboard,選擇背景視圖,然后在Attributes Inspector中將@IBInspectable值更改為以下內(nèi)容:

  • Light Color: RGB(255, 255, 242)
  • Dark Color: RGB(223, 255, 247)
  • Pattern Size: 30

使用繪制背景圖案進(jìn)行更多實(shí)驗(yàn)。 看看你是否可以將波爾卡圓點(diǎn)圖案作為背景而不是三角形。

當(dāng)然,您可以將自己的非矢量圖像替換為重復(fù)樣式。


Drawing Images - 繪圖圖像

在本教程的最后一段,你將獲得一枚獎(jiǎng)?wù)拢元?jiǎng)勵(lì)用戶喝足夠的水。 當(dāng)計(jì)數(shù)器達(dá)到八個(gè)眼鏡的目標(biāo)時(shí),將出現(xiàn)此獎(jiǎng)?wù)隆?/p>

您將在Swift Playground中繪制它,而不是使用@IBDesignable,然后將代碼復(fù)制到UIImageView子類。雖然交互式故事板通常很有用,但它們有局限性;他們只繪制簡(jiǎn)單的代碼,當(dāng)您創(chuàng)建復(fù)雜的設(shè)計(jì)時(shí),故事板通常會(huì)過(guò)時(shí)。

在這種特殊情況下,當(dāng)用戶喝八杯水時(shí),您只需要繪制一次圖像。如果用戶永遠(yuǎn)不會(huì)達(dá)到目標(biāo),則無(wú)需制作獎(jiǎng)牌。

一旦繪制,它也不需要使用draw(_ rect :)setNeedsDisplay()重繪。

是時(shí)候把畫(huà)筆放到畫(huà)布上了。您將使用Swift playground構(gòu)建獎(jiǎng)牌視圖,然后在完成后將代碼復(fù)制到Flo項(xiàng)目中。

轉(zhuǎn)到File \ New \ Playground ....選擇Blank模板,單擊Next,將操場(chǎng)命名為MedalDrawing,然后單擊Create。

在新的playground窗口中,將playground代碼替換為:

import UIKit

let size = CGSize(width: 120, height: 200)

UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let context = UIGraphicsGetCurrentContext()!

//This code must always be at the end of the playground
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

這將創(chuàng)建繪圖上下文,就像您對(duì)圖案圖像所做的那樣。

記下最后兩行;你總是需要在playground的底部,所以你可以在playground上預(yù)覽圖像。

接下來(lái),在灰色結(jié)果列中單擊此代碼右側(cè)的方形按鈕:

let image = UIGraphicsGetImageFromCurrentImageContext()

這將在代碼下方放置預(yù)覽圖像。 圖像將隨著您對(duì)代碼所做的每次更改而更新。

通常最好做一個(gè)草圖來(lái)圍繞繪制元素所需的順序 - 看看我在構(gòu)思本教程時(shí)所做的“杰作”:

這是繪制獎(jiǎng)牌的順序:

  • 背帶(紅色)
  • 獎(jiǎng)?wù)拢ń鹕珴u變)
  • 表扣(深金色)
  • 前色帶(藍(lán)色)
  • 數(shù)字1(深金色)

請(qǐng)記住保留playground的最后兩行(從最后的上下文中提取圖像的位置),并在這些行之前將此繪圖代碼添加到playground

首先,設(shè)置您需要的非標(biāo)準(zhǔn)顏色。

//Gold colors
let darkGoldColor = UIColor(red: 0.6, green: 0.5, blue: 0.15, alpha: 1.0)
let midGoldColor = UIColor(red: 0.86, green: 0.73, blue: 0.3, alpha: 1.0)
let lightGoldColor = UIColor(red: 1.0, green: 0.98, blue: 0.9, alpha: 1.0)

這一切現(xiàn)在都應(yīng)該很熟悉了。 請(qǐng)注意,當(dāng)您聲明顏色時(shí),顏色會(huì)顯示在playground的右邊緣。

添加絲帶紅色部分的繪圖代碼:

//Lower Ribbon
let lowerRibbonPath = UIBezierPath()
lowerRibbonPath.move(to: CGPoint(x: 0, y: 0))
lowerRibbonPath.addLine(to: CGPoint(x: 40, y: 0))
lowerRibbonPath.addLine(to: CGPoint(x: 78, y: 70))
lowerRibbonPath.addLine(to: CGPoint(x: 38, y: 70))
lowerRibbonPath.close()
UIColor.red.setFill()
lowerRibbonPath.fill()

這里沒(méi)有什么新東西,只是創(chuàng)建一條路徑并填充它。 您應(yīng)該會(huì)在右側(cè)窗格中看到紅色路徑。

添加clasp的代碼:

//Clasp
let claspPath = UIBezierPath(roundedRect: CGRect(x: 36, y: 62, width: 43, height: 20), cornerRadius: 5)
claspPath.lineWidth = 5
darkGoldColor.setStroke()
claspPath.stroke()

在這里,您可以使用帶有圓角的UIBezierPath(roundedRect :),方法是使用cornerRadius參數(shù)。 扣環(huán)應(yīng)在右側(cè)窗格中繪制。

添加紀(jì)念章的代碼:

//Medallion
let medallionPath = UIBezierPath(ovalIn: CGRect(x: 8, y: 72, width: 100, height: 100))
//context.saveGState()
//medallionPath.addClip()

let colors = [darkGoldColor.cgColor, midGoldColor.cgColor, lightGoldColor.cgColor] as CFArray
let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors, locations: [0, 0.51, 1])!
context.drawLinearGradient(gradient, start: CGPoint(x: 40, y: 40), end: CGPoint(x: 40, y: 162), options: [])
//context.restoreGState()

請(qǐng)注意注釋掉的行。 這些是暫時(shí)顯示如何繪制漸變:

要將漸變放在一個(gè)角度上,使其從左上角到右下角,請(qǐng)更改漸變的結(jié)束x坐標(biāo)。 將drawLinearGradient()代碼更改為:

context.drawLinearGradient(gradient, start: CGPoint(x: 40, y: 40), end: CGPoint(x: 100, y: 160), options: [])

現(xiàn)在取消注釋獎(jiǎng)?wù)吕L圖代碼中的這三行,以創(chuàng)建一個(gè)剪切路徑來(lái)約束徽章圓圈內(nèi)的漸變。

就像在本系列的第2部分中繪制圖形時(shí)所做的那樣,在添加剪切路徑之前保存上下文的繪制狀態(tài),并在繪制漸變之后恢復(fù)它,以便不再剪切上下文。

要繪制獎(jiǎng)牌的實(shí)線內(nèi)線,請(qǐng)使用獎(jiǎng)?wù)碌膱A圈路徑,但在繪制之前縮放它。 您只需將轉(zhuǎn)換應(yīng)用于一個(gè)路徑,而不是轉(zhuǎn)換整個(gè)上下文。

在獎(jiǎng)?wù)吕L圖代碼后添加此代碼:

//Create a transform
//Scale it, and translate it right and down
var transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
transform = transform.translatedBy(x: 15, y: 30)
medallionPath.lineWidth = 2.0

//apply the transform to the path
medallionPath.apply(transform)
medallionPath.stroke()

這會(huì)將路徑縮小到原始大小的80%,然后變換路徑以使其在漸變視圖中居中。

在內(nèi)部行代碼后添加上部絲帶繪圖代碼:

//Upper Ribbon
let upperRibbonPath = UIBezierPath()
upperRibbonPath.move(to: CGPoint(x: 68, y: 0))
upperRibbonPath.addLine(to: CGPoint(x: 108, y: 0))
upperRibbonPath.addLine(to: CGPoint(x: 78, y: 70))
upperRibbonPath.addLine(to: CGPoint(x: 38, y: 70))
upperRibbonPath.close()

UIColor.blue.setFill()
upperRibbonPath.fill()

這與您為下部絲帶添加的代碼非常相似:創(chuàng)建貝塞爾路徑并填充它。

最后一步是在獎(jiǎng)牌上繪制數(shù)組1。 在上面的絲帶代碼后添加此代碼:

//Number One

//Must be NSString to be able to use draw(in:)
let numberOne = "1" as NSString
let numberOneRect = CGRect(x: 47, y: 100, width: 50, height: 50)
let font = UIFont(name: "Academy Engraved LET", size: 60)!
let numberOneAttributes = [
  NSAttributedStringKey.font: font,
  NSAttributedStringKey.foregroundColor: darkGoldColor
]
numberOne.draw(in: numberOneRect, withAttributes: numberOneAttributes)

在這里,您可以使用文本屬性定義NSString,并使用draw(_in :)將其繪制到繪圖上下文中。

看起來(lái)不錯(cuò)!

你越來(lái)越近了,但看起來(lái)有點(diǎn)二維。 有一些陰影會(huì)很好。


Shadows - 陰影

要?jiǎng)?chuàng)建陰影,您需要三個(gè)元素:顏色,偏移(陰影的距離和方向)和模糊。

playground的頂部,在定義金色之后但在// Lower Ribbon線之前,插入此陰影代碼:

//Add Shadow
let shadow: UIColor = UIColor.black.withAlphaComponent(0.80)
let shadowOffset = CGSize(width: 2.0, height: 2.0)
let shadowBlurRadius: CGFloat = 5

context.setShadow(offset: shadowOffset, blur: shadowBlurRadius, color: shadow.cgColor)

這會(huì)產(chǎn)生影子,但結(jié)果可能不是你想象的那樣。 這是為什么?

將對(duì)象繪制到上下文中時(shí),此代碼會(huì)為每個(gè)對(duì)象創(chuàng)建一個(gè)陰影。

啊,哈! 你的獎(jiǎng)?wù)掳ㄎ鍌€(gè)物體。 難怪它看起來(lái)有點(diǎn)模糊。

幸運(yùn)的是,它很容易修復(fù)。 只需使用透明層對(duì)繪圖對(duì)象進(jìn)行分組,您只需為整個(gè)組繪制一個(gè)陰影。

添加代碼以在陰影代碼之后創(chuàng)建組。 從這開(kāi)始:

context.beginTransparencyLayer(auxiliaryInfo: nil)

當(dāng)你開(kāi)始一個(gè)組時(shí),你還需要結(jié)束它,所以在playground的末尾添加下一個(gè)塊,但是在你檢索最終圖像的點(diǎn)之前:

context.endTransparencyLayer()

現(xiàn)在,您將獲得一個(gè)完整的獎(jiǎng)牌圖像,其中包含干凈整潔的陰影:

這樣就完成了playground代碼,并且你有一枚獎(jiǎng)牌來(lái)展示它!


Adding the Medal Image to an Image View - 將獎(jiǎng)?wù)聢D像添加到圖像視圖

現(xiàn)在您已經(jīng)準(zhǔn)備好了代碼來(lái)繪制獎(jiǎng)牌(順便說(shuō)一句看起來(lái)很棒),您需要將它渲染到主Flo項(xiàng)目中的UIImageView中。

切換回Flo項(xiàng)目并為圖像視圖創(chuàng)建一個(gè)新文件。

單擊File \ New \ File ...并選擇Cocoa Touch Class模板。 單擊Next,然后將類命名為MedalView。 使其成為UIImageView的子類,然后單擊Next,再單擊Create。

轉(zhuǎn)到Main.storyboard并添加UIImageView作為Counter View的子視圖。 選擇UIImageView,然后在Identity Inspector中將類更改為MedalView。

Size Inspector中,為Image View提供坐標(biāo)X = 76,Y = 147,寬度= 80和高度= 80:

Attributes Inspector中,將Content Mode更改為Aspect Fit,以便圖像自動(dòng)調(diào)整大小以適合視圖。

轉(zhuǎn)到MedalView.swift并添加一個(gè)方法來(lái)創(chuàng)建獎(jiǎng)牌:

func createMedalImage() -> UIImage {
  println("creating Medal Image")
}

這將創(chuàng)建一個(gè)日志,以便您知道何時(shí)創(chuàng)建圖像。

切換回MedalDrawingplayground,并復(fù)制除初始import UIKit之外的整個(gè)代碼。

返回MedalView.swift并將playground操作碼粘貼到createMedalImage()中。

createMedalImage()的末尾,添加:

return image!

這應(yīng)該壓縮編譯錯(cuò)誤。

在類的頂部,添加一個(gè)屬性來(lái)保存獎(jiǎng)牌圖片:

lazy var medalImage: UIImage = self.createMedalImage()

延遲聲明修飾符意味著計(jì)算密集的獎(jiǎng)牌圖像代碼僅在必要時(shí)繪制。 因此,如果用戶從不記錄飲用八杯水,則獎(jiǎng)牌繪圖代碼將永遠(yuǎn)不會(huì)運(yùn)行。

添加一個(gè)方法來(lái)顯示獎(jiǎng)牌:

func showMedal(show: Bool) {
  image = (show == true) ? medalImage : nil
}

轉(zhuǎn)到ViewController.swift并在類的頂部添加一個(gè)outlet

@IBOutlet weak var medalView: MedalView!

轉(zhuǎn)到Main.storyboard并將新的MedalView連接到此outlet。

返回ViewController.swift并將此方法添加到類中:

func checkTotal() {
  if counterView.counter >= 8 {
    medalView.showMedal(show: true)
  } else {
    medalView.showMedal(show: false)
  }
}

如果您在白天喝足夠的水,這將顯示獎(jiǎng)牌。

viewDidLoad()pushButtonPressed(_ :)的末尾調(diào)用此方法:

checkTotal()

構(gòu)建并運(yùn)行應(yīng)用程序。 它應(yīng)該如下所示:

在調(diào)試控制臺(tái)中,您將看到creating Medal Image日志僅在計(jì)數(shù)器達(dá)到8時(shí)輸出并顯示獎(jiǎng)牌,因?yàn)?code>medalImage使用延遲聲明。

后記

本篇主要講述了基于CoreGraphic的一個(gè)簡(jiǎn)單繪制示例 ,感興趣的給個(gè)贊或者關(guān)注~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容