CoreGraphic框架解析 (七)—— 基于CoreGraphic的一个简单绘制示例 (三)

版本记录

版本号 时间
V1.0 2018.10.21 星期日

前言

quartz是一个通用的术语,用于描述在iOSMAC OS X 中整个媒体层用到的多种技术 包括图形、动画、音频、适配。Quart 2D 是一组二维绘图和渲染APICore Graphic会使用到这组APIQuartz Core专指Core Animation用到的动画相关的库、API和类。CoreGraphicsUIKit下的主要绘图系统,频繁的用于绘制自定义视图。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.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)
  }
}

您的故事板的背景视图现在应该是黄色的。 以上代码的更多细节:

  • 1) lightColordarkColor具有@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")
}

这将创建一个日志,以便您知道何时创建图像。

切换回MedalDrawingplayground,并复制除初始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的一个简单绘制示例 ,感兴趣的给个赞或者关注~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容