版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.10.21 星期日 |
前言
quartz
是一个通用的术语,用于描述在iOS
和MAC OS X
中整个媒体层用到的多种技术 包括图形、动画、音频、适配。Quart 2D
是一组二维绘图和渲染API
,Core Graphic
会使用到这组API
,Quartz Core
专指Core Animation
用到的动画相关的库、API
和类。CoreGraphics
是UIKit
下的主要绘图系统,频繁的用于绘制自定义视图。Core Graphics
是高度集成于UIView
和其他UIKit
部分的。Core Graphics
数据结构和函数可以通过前缀CG
来识别。在app中很多时候绘图等操作我们要利用CoreGraphic
框架,它能绘制字符串、图形、渐变色等等,是一个很强大的工具。感兴趣的可以看我另外几篇。
1. CoreGraphic框架解析(一)—— 基本概览
2. CoreGraphic框架解析(二)—— 基本使用
3. CoreGraphic框架解析(三)—— 类波浪线的实现
4. CoreGraphic框架解析(四)—— 基本架构补充
5. CoreGraphic框架解析 (五)—— 基于CoreGraphic的一个简单绘制示例 (一)
6. CoreGraphic框架解析 (六)—— 基于CoreGraphic的一个简单绘制示例 (二)
开始
在我们的Core Graphics
教程的第三部分也是最后一部分中,您将把Flo
带到最终形式。 具体来说,你将:
- 为背景创建重复模式。
- 从头到尾画一枚奖牌,奖励用户每天成功饮用八杯水。
您在本节中的任务是使用UIKit的pattern
方法来创建此背景模式:
转到File \ New \ File ...
并选择iOS iOS \ Source \ Cocoa Touch Class
模板以创建一个名为BackgroundView
的类,其为UIView
子类。 单击Next,然后单击Create。
转到Main.storyboard
,选择ViewController
的主视图,并在Identity Inspector
中将类更改为BackgroundView
。
使用Assistant Editor
设置BackgroundView.swift
和Main.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)
}
}
您的故事板的背景视图现在应该是黄色的。 以上代码的更多细节:
- 1)
lightColor
和darkColor
具有@IBInspectable
属性,因此以后更容易配置背景颜色。 你使用橙色和黄色作为临时颜色,这样你就可以看到发生了什么。patternSize
控制重复模式的大小。 它最初设置为大型,因此很容易看出发生了什么。 - 2)
UIGraphicsGetCurrentContext()
为您提供视图的上下文,也是draw(_ rect :)
绘制的地方。 - 3) 使用
Core Graphics
方法setFillColor()
设置上下文的当前填充颜色。 请注意,在使用Core Graphics
时,您需要使用CGColor
,这是darkColor
的一个属性。 - 4)
fill()
不是设置矩形路径,而是使用当前填充颜色填充整个上下文。
您现在要使用UIBezierPath()
绘制这三个橙色三角形。 这些数字对应于以下代码中的点:
仍然在BackgroundView.swift
中,将此代码添加到draw(_ rect:)
结束:
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()
请注意您如何使用一条路径绘制三个三角形。move(to:)
就像在绘图并将其移动到新位置时从纸上抬起笔。
您的故事板现在应该在背景视图的左上角有一个橙色和黄色的图像。
到目前为止,您已直接绘制到视图的绘图上下文中。 为了能够重复此图案(pattern)
,您需要在上下文之外创建一个图像,然后将该图像用作上下文中的模式。
找到以下内容。 它接近于draw(_ 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))
嘿! 那些橙色三角形从故事板上消失了。 他们去哪儿了?
UIGraphicsBeginImageContextWithOptions()
创建一个新的上下文并将其设置为当前绘图上下文,因此您现在正在绘制这个新的上下文。 该方法的参数是:
- 上下文的大小。
- 上下文是否不透明 - 如果您需要透明度,那么这需要是错误的。
- 上下文的
scale
。 如果您正在使用视网膜屏幕,则应为2.0,如果是iPhone 6 Plus,则应为3.0。 但是,这使用0.0,这可确保自动应用设备的正确比例。
然后,您使用UIGraphicsGetCurrentContext()
来获取对此新上下文的引用。
然后用黄色填充新的上下文。 您可以通过将上下文不透明度设置为false
来让原始背景显示,但绘制不透明上下文比绘制透明更快,并且该参数足以变为不透明。
将此代码添加到draw(_ rect:)
结束:
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
这从当前上下文中提取UIImage
。 当您使用UIGraphicsEndImageContext()
结束当前上下文时,绘图上下文将恢复为视图的上下文,因此draw(_ rect:)
中的任何进一步绘图都会出现在视图中。
要将图像绘制为重复模式,请将此代码添加到draw(_ rect:)
结束:
UIColor(patternImage: image).setFill()
context.fill(rect)
这通过将图像用作颜色而不是纯色来创建新的UIColor
。
构建并运行应用程序。 您现在应该拥有相当明亮的应用背景。
转到Main.storyboard,选择背景视图,然后在Attributes Inspector
中将@IBInspectable
值更改为以下内容:
Light Color: RGB(255, 255, 242)
Dark Color: RGB(223, 255, 247)
Pattern Size: 30
使用绘制背景图案进行更多实验。 看看你是否可以将波尔卡圆点图案作为背景而不是三角形。
当然,您可以将自己的非矢量图像替换为重复样式。
Drawing Images - 绘图图像
在本教程的最后一段,你将获得一枚奖章,以奖励用户喝足够的水。 当计数器达到八个眼镜的目标时,将出现此奖章。
您将在Swift Playground
中绘制它,而不是使用@IBDesignable
,然后将代码复制到UIImageView
子类。虽然交互式故事板通常很有用,但它们有局限性;他们只绘制简单的代码,当您创建复杂的设计时,故事板通常会过时。
在这种特殊情况下,当用户喝八杯水时,您只需要绘制一次图像。如果用户永远不会达到目标,则无需制作奖牌。
一旦绘制,它也不需要使用draw(_ rect :)
和setNeedsDisplay()
重绘。
是时候把画笔放到画布上了。您将使用Swift playground
构建奖牌视图,然后在完成后将代码复制到Flo
项目中。
转到File \ New \ Playground ....
选择Blank
模板,单击Next,将操场命名为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()
这将创建绘图上下文,就像您对图案图像所做的那样。
记下最后两行;你总是需要在playground
的底部,所以你可以在playground
上预览图像。
接下来,在灰色结果列中单击此代码右侧的方形按钮:
let image = UIGraphicsGetImageFromCurrentImageContext()
这将在代码下方放置预览图像。 图像将随着您对代码所做的每次更改而更新。
通常最好做一个草图来围绕绘制元素所需的顺序 - 看看我在构思本教程时所做的“杰作”:
这是绘制奖牌的顺序:
- 背带(红色)
- 奖章(金色渐变)
- 表扣(深金色)
- 前色带(蓝色)
- 数字1(深金色)
请记住保留playground
的最后两行(从最后的上下文中提取图像的位置),并在这些行之前将此绘图代码添加到playground
:
首先,设置您需要的非标准颜色。
//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)
这一切现在都应该很熟悉了。 请注意,当您声明颜色时,颜色会显示在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()
这里没有什么新东西,只是创建一条路径并填充它。 您应该会在右侧窗格中看到红色路径。
添加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
参数。 扣环应在右侧窗格中绘制。
添加纪念章的代码:
//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()
请注意注释掉的行。 这些是暂时显示如何绘制渐变:
要将渐变放在一个角度上,使其从左上角到右下角,请更改渐变的结束x坐标。 将drawLinearGradient()
代码更改为:
context.drawLinearGradient(gradient, start: CGPoint(x: 40, y: 40), end: CGPoint(x: 100, y: 160), options: [])
现在取消注释奖章绘图代码中的这三行,以创建一个剪切路径来约束徽章圆圈内的渐变。
就像在本系列的第2部分中绘制图形时所做的那样,在添加剪切路径之前保存上下文的绘制状态,并在绘制渐变之后恢复它,以便不再剪切上下文。
要绘制奖牌的实线内线,请使用奖章的圆圈路径,但在绘制之前缩放它。 您只需将转换应用于一个路径,而不是转换整个上下文。
在奖章绘图代码后添加此代码:
//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()
这会将路径缩小到原始大小的80%,然后变换路径以使其在渐变视图中居中。
在内部行代码后添加上部丝带绘图代码:
//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()
这与您为下部丝带添加的代码非常相似:创建贝塞尔路径并填充它。
最后一步是在奖牌上绘制数组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 :)
将其绘制到绘图上下文中。
看起来不错!
你越来越近了,但看起来有点二维。 有一些阴影会很好。
Shadows - 阴影
要创建阴影,您需要三个元素:颜色,偏移(阴影的距离和方向)和模糊。
在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)
这会产生影子,但结果可能不是你想象的那样。 这是为什么?
将对象绘制到上下文中时,此代码会为每个对象创建一个阴影。
啊,哈! 你的奖章包括五个物体。 难怪它看起来有点模糊。
幸运的是,它很容易修复。 只需使用透明层对绘图对象进行分组,您只需为整个组绘制一个阴影。
添加代码以在阴影代码之后创建组。 从这开始:
context.beginTransparencyLayer(auxiliaryInfo: nil)
当你开始一个组时,你还需要结束它,所以在playground
的末尾添加下一个块,但是在你检索最终图像的点之前:
context.endTransparencyLayer()
现在,您将获得一个完整的奖牌图像,其中包含干净整洁的阴影:
这样就完成了playground
代码,并且你有一枚奖牌来展示它!
Adding the Medal Image to an Image View - 将奖章图像添加到图像视图
现在您已经准备好了代码来绘制奖牌(顺便说一句看起来很棒),您需要将它渲染到主Flo
项目中的UIImageView
中。
切换回Flo
项目并为图像视图创建一个新文件。
单击File \ New \ File ...
并选择Cocoa Touch Class
模板。 单击Next
,然后将类命名为MedalView
。 使其成为UIImageView
的子类,然后单击Next,再单击Create。
转到Main.storyboard
并添加UIImageView
作为Counter View
的子视图。 选择UIImageView
,然后在Identity Inspector
中将类更改为MedalView
。
在Size Inspector
中,为Image View
提供坐标X = 76,Y = 147,宽度= 80和高度= 80:
在Attributes Inspector
中,将Content Mode
更改为Aspect Fit
,以便图像自动调整大小以适合视图。
转到MedalView.swift
并添加一个方法来创建奖牌:
func createMedalImage() -> UIImage {
println("creating Medal Image")
}
这将创建一个日志,以便您知道何时创建图像。
切换回MedalDrawing
的playground
,并复制除初始import UIKit
之外的整个代码。
返回MedalView.swift
并将playground
操作码粘贴到createMedalImage()
中。
在createMedalImage()
的末尾,添加:
return image!
这应该压缩编译错误。
在类的顶部,添加一个属性来保存奖牌图片:
lazy var medalImage: UIImage = self.createMedalImage()
延迟声明修饰符意味着计算密集的奖牌图像代码仅在必要时绘制。 因此,如果用户从不记录饮用八杯水,则奖牌绘图代码将永远不会运行。
添加一个方法来显示奖牌:
func showMedal(show: Bool) {
image = (show == true) ? medalImage : nil
}
转到ViewController.swift
并在类的顶部添加一个outlet
:
@IBOutlet weak var medalView: MedalView!
转到Main.storyboard
并将新的MedalView
连接到此outlet
。
返回ViewController.swift
并将此方法添加到类中:
func checkTotal() {
if counterView.counter >= 8 {
medalView.showMedal(show: true)
} else {
medalView.showMedal(show: false)
}
}
如果您在白天喝足够的水,这将显示奖牌。
在viewDidLoad()
和pushButtonPressed(_ :)
的末尾调用此方法:
checkTotal()
构建并运行应用程序。 它应该如下所示:
在调试控制台中,您将看到creating Medal Image
日志仅在计数器达到8时输出并显示奖牌,因为medalImage
使用延迟声明。
后记
本篇主要讲述了基于CoreGraphic的一个简单绘制示例 ,感兴趣的给个赞或者关注~~~