版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2018.10.22 星期一 |
前言
quartz
是一個(gè)通用的術(shù)語醉拓,用于描述在iOS
和MAC OS X
中整個(gè)媒體層用到的多種技術(shù) 包括圖形蕾总、動(dòng)畫囤屹、音頻笑跛、適配付魔。Quart 2D
是一組二維繪圖和渲染API
,Core Graphic
會(huì)使用到這組API
飞蹂,Quartz Core
專指Core Animation
用到的動(dòng)畫相關(guān)的庫几苍、API
和類。CoreGraphics
是UIKit
下的主要繪圖系統(tǒng)陈哑,頻繁的用于繪制自定義視圖妻坝。Core Graphics
是高度集成于UIView
和其他UIKit
部分的。Core Graphics
數(shù)據(jù)結(jié)構(gòu)和函數(shù)可以通過前綴CG
來識(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)單繪制示例 (二)
7. CoreGraphic框架解析 (七)—— 基于CoreGraphic的一個(gè)簡(jiǎn)單繪制示例 (三)
源碼
1. Swift
該教程最大的特點(diǎn)就是界面完全是用代碼繪制的售担,asset里面沒有任何東西赁遗,是一個(gè)完全由CoreGraphic進(jìn)行繪制的小示例。
首先看一下項(xiàng)目組織結(jié)構(gòu)族铆。
接著看一下sb中的內(nèi)容
下面就是源碼了
1. ViewController.swift
import UIKit
class ViewController: UIViewController {
//Counter outlets
@IBOutlet weak var counterView: CounterView!
@IBOutlet weak var counterLabel: UILabel!
@IBOutlet weak var containerView: UIView!
@IBOutlet weak var graphView: GraphView!
//Label outlets
@IBOutlet weak var averageWaterDrunk: UILabel!
@IBOutlet weak var maxLabel: UILabel!
@IBOutlet weak var stackView: UIStackView!
@IBOutlet weak var medalView: MedalView!
var isGraphViewShowing = false
@IBAction func pushButtonPressed(_ button: PushButton) {
if button.isAddButton {
counterView.counter += 1
} else {
if counterView.counter > 0 {
counterView.counter -= 1
}
}
counterLabel.text = String(counterView.counter)
if isGraphViewShowing {
counterViewTap(nil)
}
checkTotal()
}
@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
setupGraphDisplay()
UIView.transition(from: counterView,
to: graphView,
duration: 1.0,
options: [.transitionFlipFromRight, .showHideTransitionViews],
completion: nil)
}
isGraphViewShowing = !isGraphViewShowing
}
override func viewDidLoad() {
super.viewDidLoad()
counterLabel.text = String(counterView.counter)
checkTotal()
}
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 day
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)
}
}
}
func checkTotal() {
if counterView.counter >= 8 {
medalView.showMedal(show: true)
} else {
medalView.showMedal(show: false)
}
}
}
2. PushButton.swift
import UIKit
@IBDesignable
class PushButton: UIButton {
private struct Constants {
static let plusLineWidth: CGFloat = 3.0
static let plusButtonScale: CGFloat = 0.6
static let halfPointShift: CGFloat = 0.5
}
private var halfWidth: CGFloat {
return bounds.width / 2
}
private var halfHight: CGFloat {
return bounds.height / 2
}
@IBInspectable var fillColor: UIColor = UIColor.green
@IBInspectable var isAddButton: Bool = true
override func draw(_ rect: CGRect) {
let path = UIBezierPath(ovalIn: rect)
fillColor.setFill()
path.fill()
//set up the width and height variables
//for the horizontal stroke
let plusWidth: CGFloat = min(bounds.width, bounds.height) * Constants.plusButtonScale
let halfPlusWidth = plusWidth / 2
//create the path
let plusPath = UIBezierPath()
//set the path's line width to the height of the stroke
plusPath.lineWidth = Constants.plusLineWidth
//move the initial point of the path
//to the start of the horizontal stroke
plusPath.move(to: CGPoint(
x: halfWidth - halfPlusWidth + Constants.halfPointShift,
y: halfHight + Constants.halfPointShift))
//add a point to the path at the end of the stroke
plusPath.addLine(to: CGPoint(
x: halfWidth + halfPlusWidth + Constants.halfPointShift,
y: halfHight + Constants.halfPointShift))
//Vertical Line
if isAddButton {
plusPath.move(to: CGPoint(
x: halfWidth + Constants.halfPointShift,
y: halfHight - halfPlusWidth + Constants.halfPointShift))
plusPath.addLine(to: CGPoint(
x: halfWidth + Constants.halfPointShift,
y: halfHight + halfPlusWidth + Constants.halfPointShift))
}
//existing code
//set the stroke color
UIColor.white.setStroke()
plusPath.stroke()
}
}
3. CounterView.swift
import UIKit
@IBDesignable class CounterView: UIView {
private struct Constants {
static let numberOfGlasses = 8
static let lineWidth: CGFloat = 5.0
static let arcWidth: CGFloat = 76
static var halfOfLineWidth: CGFloat {
return lineWidth / 2
}
}
@IBInspectable var counter: Int = 5 {
didSet {
if counter <= Constants.numberOfGlasses {
//the view needs to be refreshed
setNeedsDisplay()
}
}
}
@IBInspectable var outlineColor: UIColor = UIColor.blue
@IBInspectable var counterColor: UIColor = UIColor.orange
override func draw(_ rect: CGRect) {
// 1
let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2)
// 2
let radius: CGFloat = max(bounds.width, bounds.height)
// 3
let startAngle: CGFloat = 3 * .pi / 4
let endAngle: CGFloat = .pi / 4
// 4
let path = UIBezierPath(arcCenter: center,
radius: radius/2 - Constants.arcWidth/2,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
// 5
path.lineWidth = Constants.arcWidth
counterColor.setStroke()
path.stroke()
//Draw the outline
//1 - first calculate the difference between the two angles
//ensuring it is positive
let angleDifference: CGFloat = 2 * .pi - startAngle + endAngle
//then calculate the arc for each single glass
let arcLengthPerGlass = angleDifference / CGFloat(Constants.numberOfGlasses)
//then multiply out by the actual glasses drunk
let outlineEndAngle = arcLengthPerGlass * CGFloat(counter) + startAngle
//2 - draw the outer arc
let outlinePath = UIBezierPath(arcCenter: center,
radius: bounds.width/2 - Constants.halfOfLineWidth,
startAngle: startAngle,
endAngle: outlineEndAngle,
clockwise: true)
//3 - draw the inner arc
outlinePath.addArc(withCenter: center,
radius: bounds.width/2 - Constants.arcWidth + Constants.halfOfLineWidth,
startAngle: outlineEndAngle,
endAngle: startAngle,
clockwise: false)
//4 - close the path
outlinePath.close()
outlineColor.setStroke()
outlinePath.lineWidth = Constants.lineWidth
outlinePath.stroke()
//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()
}
}
4. GraphView.swift
import UIKit
@IBDesignable class GraphView: UIView {
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
}
//1 - the properties for the gradient
@IBInspectable var startColor: UIColor = .red
@IBInspectable var endColor: UIColor = .green
//Weekly sample data
var graphPoints: [Int] = [4, 2, 6, 4, 5, 8, 3]
override func draw(_ rect: CGRect) {
let width = rect.width
let height = rect.height
let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: UIRectCorner.allCorners,
cornerRadii: Constants.cornerRadiusSize)
path.addClip()
//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 = CGGradient(colorsSpace: colorSpace,
colors: colors as CFArray,
locations: colorLocations)!
//6 - draw the gradient
var startPoint = CGPoint.zero
var endPoint = CGPoint(x: 0, y: self.bounds.height)
context.drawLinearGradient(gradient,
start: startPoint,
end: endPoint,
options: CGGradientDrawingOptions(rawValue: 0))
//calculate the x point
let margin = Constants.margin
let 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
}
// calculate the y point
let topBorder: CGFloat = Constants.topBorder
let bottomBorder: CGFloat = Constants.bottomBorder
let graphHeight = height - topBorder - bottomBorder
let maxValue = graphPoints.max()!
let columnYPoint = { (graphPoint:Int) -> CGFloat in
var y:CGFloat = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight
y = graphHeight + topBorder - y // Flip the graph
return y
}
// 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)
}
//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()
let highestYPoint = columnYPoint(maxValue)
startPoint = CGPoint(x:margin, y: highestYPoint)
endPoint = CGPoint(x:margin, y:self.bounds.height)
context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: CGGradientDrawingOptions(rawValue: 0))
context.restoreGState()
//draw the line on top of the clipped gradient
graphPath.lineWidth = 2.0
graphPath.stroke()
//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 -= 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()
}
//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()
}
}
5. 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)
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))
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()
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
UIColor(patternImage: image).setFill()
context.fill(rect)
}
}
6. MedalView.swift
import UIKit
class MedalView: UIImageView {
lazy var medalImage: UIImage = self.createMedalImage()
func createMedalImage() -> UIImage {
debugPrint("creating Medal Image")
let size = CGSize(width: 120, height: 200)
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let context = UIGraphicsGetCurrentContext()!
//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)
//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)
context.beginTransparencyLayer(auxiliaryInfo: nil)
//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()
//Clasp
let claspPath = UIBezierPath(roundedRect: CGRect(x: 36, y: 62, width: 43, height: 20), cornerRadius: 5)
claspPath.lineWidth = 5
darkGoldColor.setStroke()
claspPath.stroke()
//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: 100, y: 160), options: [])
context.restoreGState()
//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()
//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()
//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)
context.endTransparencyLayer()
//This code must always be at the end of the playground
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
func showMedal(show:Bool) {
image = show == true ? medalImage : nil
}
}
7. MedalDrawing.playground
import UIKit
let size = CGSize(width: 120, height: 200)
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let context = UIGraphicsGetCurrentContext()!
//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)
//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)
context.beginTransparencyLayer(auxiliaryInfo: nil)
//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()
//Clasp
let claspPath = UIBezierPath(roundedRect: CGRect(x: 36, y: 62, width: 43, height: 20), cornerRadius: 5)
claspPath.lineWidth = 5
darkGoldColor.setStroke()
claspPath.stroke()
//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: 100, y: 160), options: [])
context.restoreGState()
//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()
//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()
//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)
context.endTransparencyLayer()
//This code must always be at the end of the playground
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
后記
本篇主要講述了基于CoreGraphic的一個(gè)簡(jiǎn)單繪制示例岩四,感興趣的給個(gè)贊或者關(guān)注~~~