Core Graphics教程第一部分(Swift) – 起步

泡在网上的日子 / 文 发表于2015-05-25 18:12 次阅读 Swift

想象一下…当你完成了你的应用,它能够很好地运行,但是界面还缺少个人的风格。这时,你可以选择用PS画出一系列尺寸的图片,并希望Apple不会再推出一个@4x的retina显示尺寸…

或者,你可以领先一步,采用Core Graphics进行绘图,保证一个图片可以适应各种尺寸的屏幕。

Core Graphics是苹果的一个矢量图绘制框架 – 它是一个大型且给力的API、有很多需要去学习。不过别慌,这个系列的文章会带你由简入深地学习Core Graphics,最后你将可以在你的应用中创作令人惊叹的图片。

这是一个全新的系列,采用最新的方式来学习Core Graphics。这个系列完全和Xcode6与Swift保持同步,并且包含了一些很酷的新特性,比如@IBDesignable@IBInspectable,这让学习Core Graphics变得更有趣也更容易。

拿起可乐,让我们开始吧!

介绍Flo – 一款记录喝水杯数的App

你将会完成一个追踪你喝水习惯的app。

Flo可以让你很轻松地追踪到你喝了多少水。人们说一天喝8杯水才是健康的,但是往往喝了几杯水之后我们就忘了记录了。这就是Flo使用的时候了, 每当你喝完一杯水以后,你就可以在Flo上点击计数,Flo会为你记录喝水情况。你也可以在Flo上查看到过去7天的喝水状况。

demo

在这个系列的第一部分,你将会通过UIKit的画图方法创建三个控件。
第二部分中,你将会深入了解Core Graphics的内容并且学习绘图。
最后第三部分,你将会制作一个样式背景,并且获得自制的Core Graphics奖牌 :)

让我们开始吧

你的第一个任务是新建你自己的Flo应用。这边不会提供任何下载,因为只有从零开始,你才能学得更多。

创建一个新项目(File\New\Project…),选择模板iOS\Application\Single View Application,然后点击Next

弹出一个项目选项框,把项目名设为Flo,语言设置为Swift,设备选为iPhone,然后点击Next

start

在最后一步,不要选择Git repository,并点击Create
现在你已经完成了一个拥有storyboard和view controller的初始项目

在View上画图

在画图的时候,你需要实行以下三个步骤:

  1. 创建一个UIView子类。

  2. 重写drawRect(_:)并添加一些Core Graphics绘图代码。

  3. 想什么呢!没有第三步了 :)

让我们尝试一下画一个自制的加号按钮。

button

创建一个新文件(File\New\File…),选择iOS\Source\Cocoa Touch Class,取名为PushButtonView。让它继承于UIButton,确保选择的语言是Swift。点击Next并完成Create
由于UIButtonUIView的子类,UIView的所有方法,像drawRect(_:),在UIButton里同样存在。
Main.storyboard里,将UIButton拖到View Controller的view里,并选择Document Outline
Identity Inspector把类改成你自己的PushButtonView

pushButtonView

Size Inspector里设置X=250, Y=350, Width=100, Height=100:

sizeInspector

自动布局约束

现在你将要设置自动布局约束(文字指导在后面):

setUpAutoLayout

  • 选中button,按住control从按钮的中心轻轻拖到左侧(仍旧在按钮内),在弹出的菜单中选择Width。

  • 同样地,用上述方式从中间向上拖动(仍旧在按钮内),在弹出的菜单中选择Height。

  • 按住control从按钮内向左拖动,直到按钮外,选择Center Vertically in Container。

  • 最后,同样地由下至上从按钮内到按钮外,选择Center Horizontally in Container。
    (以上几步中按住control和点击鼠标右键拖动的效果是一样的)
    这几步会创建四个需要的auto layout constraints,你可以在Size Inspector看到他们:

sizeInspector

在Attributes Inspector里移除默认的文字“Button”。

Attributes

你可以在此刻编译运行程序,不过现在你只能看到一片空白,让我们来继续吧!

画一个按钮

回忆一下,你试着画的按钮是一个圆形:

button

为了实现在Core Graphics画一个图形,你需要定义一个路径来告诉Core Graphics如何画线(就像画一个加号需要两条直线)或者是需要填充的线(像这里的圆)。如果你很熟悉Illustrator或者Photoshop 里的矢量图形,你可以很容易地就理解路径。
这里有三个路径的基本原则:

  • 路径可以被描边或者填充。

  • 可以用当前的描边颜色为路径勾边。

  • 可以用当前的填充颜色为闭合路径围起来的区域进行填充。

创建Core Graphics路径的一个简单方法是采用一个好用的类UIBezierPath。这个类可以让你通过一个友好的API来创建路径,无论是你想基于线段、曲线、长方形或者是一系列的连接点。
让我们尝试用UIBezierPath来创建一个路径,然后用绿色来填充它。打开PushButtonView.swift,并添加以下方法:

override func drawRect(rect: CGRect) {
  var path = UIBezierPath(ovalInRect: rect)
  UIColor.greenColor().setFill()
 path.fill()
}

首先,你创建了一个椭圆型的UIBezierPath,尺寸由长方形的大小决定。在本文的例子中,由于我们在storyboard里定义了100×100的按钮,所以“椭圆”其实是一个圆。
路径本身不会画任何东西。即使没有可绘图的上下文,你也可以定义路径。如果要绘制路径,你需要给当前的上下文一个填充颜色,然后填充路径。

编译并运行应用,你会看到一个绿色的圆。

haha

到这里,你会发现做一个自定义的view是如此简单。你通过创建一个UIButton的子类、重写drawRect(_:)、把UIButton放进你的storyboard中这几个简单的步骤完成了所有工作。

haha

Core Graphics的背后实现

每一个UIView都会有一个图形上下文,所有针对这个UIView的绘图都会先绘制到此上下文,然后再转换到设备的屏幕上。

iOS更新上下文的方法是drawRect(_:),这个方法会在以下四种情况被调用:

  • 屏幕第一次显示这个view。

  • 它顶部的view被移除。

  • view的hidden属性被修改。

  • 在view上调用了了setNeedsDisplay()与setNeedsDisplayInRect()方法。

注意:所有图形上下文的绘画都需要在drawRect(_:)中完成。最后一章中,你需要创建自己的图形上下文,你需要注意是不是在drawRect(_:)以外进行绘图了。

你还没有在本教程中使用Core Graphics,因为UIKit封装了很多Core Graphics的方法。比如UIBezierPath就是对CGMutablePath的封装,CGMutablePath是Core Graphics的底层API。

Note: Never call drawRect(_:) directly. If your view is not being updated, then call setNeedsDisplay() on the view.

注意:不要直接调用drawRect(_:),如果你的view并没有被更新,你可以在view上调用setNeedsDisplay()。setNeedsDisplay()不会自己调用drawRect(_:),但它会把view标记为“未刷新”,并触发drawRect(_:)。所以,即使你在一个方法中调用了五次setNeedsDisplay(),但实际中只会运行一次drawRect(_:)

@IBDesignable – 可交互绘图

除了写绘图代码然后运行app看效果之外,你还有其他选择。一个Xcode6的新功能是Live Rendering,你可以设置view为@IBDesignable属性,当你在drawRect(_:)更新view的时候,它会马上在storyboard上展示出来。

在PushButtonView.swift里, 在类的声明前加上:

@IBDesignable

这样就可以开启Live Rendering。

现在把你的屏幕设置为同时可以看代码和storyboard。
方法是:选择PushButtonView.swift现实代码,在顶部右边点击Assistant Editor(它的图标是两个环绕的圆环)。storyboard会在右边展示。如果不是的话,你需要顶部选择到storyboard。

3

关闭storyboard左边的文件导航栏,这样可以多留出一些空间。可以通过拖动边界或者点击storyboard左下方的按钮实现:

1

一切都完成后,你看到的是这样的:

2

在PushButtonView中把drawRect(_:)

UIColor.greenColor().setFill()

改成

UIColor.blueColor().setFill()

你会直接在storyboard上看到改变。非常酷~

3

在上下文进行绘图

Core Graphics使用的是“绘画者模型”。

当你在上下文中绘图时,就和真实世界中的绘图一样。你先画一条路径,然后填充颜色,接着你再画一条线,再继续填充。你不能改变已经画好的像素点,但是你可以在他们的上面继续作画。

这张苹果官方文档的图片描述了是如何运作的,当你在画板上绘图时,你画图的顺序是非常重要的

4

你的加号符号应该在蓝色圆圈的上面,所以你需要先画圆,再画加号。

你可以画两个长方形来完成加号,但如果用粗线绘制路径会更简单一点。

把这些代码加在drawRect(_:)的最后,用来画加号的水平线:

//set up the width and height variables
//for the horizontal stroke
let plusHeight: CGFloat = 3.0
let plusWidth: CGFloat = min(bounds.width, bounds.height) * 0.6
 
//create the path
var plusPath = UIBezierPath()
 
//set the path's line width to the height of the stroke
plusPath.lineWidth = plusHeight
 
//move the initial point of the path
//to the start of the horizontal stroke
plusPath.moveToPoint(CGPoint(
  x:bounds.width/2 - plusWidth/2,
  y:bounds.height/2))
 
//add a point to the path at the end of the stroke
plusPath.addLineToPoint(CGPoint(
  x:bounds.width/2 + plusWidth/2,
  y:bounds.height/2))
 
//set the stroke color
UIColor.whiteColor().setStroke()
 
//draw the stroke
plusPath.stroke()

在这一部分中,你先设置了一个UIBezierPath,给它了一个起始为止(圆圈的左侧),然后画到重点(圆圈的右侧)。然后你设置画壁颜色为白色,这个时候你可以在Storyboard中看到如下:

在Storyboard中,你将看到蓝色圆圈中包含了一条水平的白线:

1

注意:记住一条路径可以由点组成。这里有一个比较容易理解的理论:当你在创建路径时,想象此时你的手中握着笔。放两个点在纸上,然后把笔放在一个起点,然后往另一个点画线。这就是上述代码中moveToPoint(_:)和addLineToPoint(_:)所做的事。

现在在ipad2和iphone6+的模拟器上运行代码,你会发现横线没有之前显示的那么好了,在它周围产生了淡蓝色的边框。

1

Points和Pixels

在早期的iphone中,Points和Pixels所占的空间与大小是相同的。当retina屏幕的iphone问世后,屏幕上Pixels的数量变成了Points的四倍。

同样地,iphone6+在相同points的基础上再一次增加了pixels的数量。

注意:以下只是概念 – 真实的硬件像素可能是不同的。如果要了解更多iphone6+的案例,可以看下这个文章

这里是一个12×12的pixel矩阵,其中point以灰色和白色表示。第一个是ipad2,一个pixel对应一个point。第二个是 iphone6,是一个2x的retina屏幕,4个pixel对应一个point。第三个是iphone6+,是一个3x的retina屏幕,9个 pixel对应一个point。

1

你画的线的高度是3points,两边的宽度各是1.5points。

这张图展示了3-point的线在各个设备上的情况。你可以看到ipad2和iphone6+都发生了覆盖一半pixel的情况,这是无法实现的。所以在实际中iOS会采用两种颜色的中间色,所以线看起来会模糊。

1

在实际中,iPhone6+有很多pixels,可能你不会注意到这些模糊的情况,但是你应该在你的app里注意尽量避免。不过如果你是为了非retina屏的设备进行开发,那么你就需要避免这类情况的发生。

如果你要画奇数的直线,你需要加上或减去0.5points来避免模糊。如果你看一下上面的图表,你会看到增加0.5points相当于是在 ipad上增加0.5pixels,在iphone6的线顶部上增加1pixel,在iphone6+的顶部增加1.5pixels。

在drawRect(_:)中,把moveToPoint与addLineToPoint的代码替换为:

//move the initial point of the path
//to the start of the horizontal stroke
plusPath.moveToPoint(CGPoint(
  x:bounds.width/2 - plusWidth/2 + 0.5,
  y:bounds.height/2 + 0.5))
 
//add a point to the path at the end of the stroke
plusPath.addLineToPoint(CGPoint(
  x:bounds.width/2 + plusWidth/2 + 0.5,
  y:bounds.height/2 + 0.5))

现在在三种设备上都能很清晰地展示线条了,因为你对其进行了半个point的调整。

注意:为了线条的完美,你可以采用填充UIBezierPath(rect:)方法来代替画线条,并通过view的contentScaleFactor计算出长方形的高和宽。和画线方法会从路径的中间点开始向外计算宽度,而填充只会在路径内部进行。

在上述两行代码后面继续加上一条垂直线段,最后在drawRect(_:)里设置画线的颜色。我想在上述步骤之后,你应该已经知道该怎么完成了:

你可以在storyboard中实时看到最新的图案,这是最终完成的加号按钮:

1

@IBInspectable – 自定义Storyboard属性

你知道,必须要点击一个没有需求的按钮,却只是为了确认保存是一件多么令人抓狂的事情吗?所以,你需要提供一种方式让用户从这种过于泛滥的点击中解脱出来——你需要一个减号按钮。

减号按钮与加号按钮基本一致,除了它没有一条竖线和并且是另外一种颜色。你需要用同样的PushButtonView类来定义减号按钮,并且在你将按钮添加到storyboard时,需要声明这个按钮的类型和颜色。

为了使得按钮可以被Interfase Builder读取,你需要添加@IBInspectable这一变量给按钮的属性。这也意味着,你可以在storyboard里面定义按钮的颜色,而不是在代码中编写。

在PushButtonView类的顶部,添加两个属性:

@IBInspectable var fillColor: UIColor = UIColor.greenColor()
@IBInspectable var isAddButton: Bool = true

从drawRect(_:)的顶部到

UIColor.blueColor().setFill()

更改和填充颜色相关的代码

fillColor.setFill()

在storyboard的视图中,按钮就会变成绿色。

和垂直相关的代码在drawRect(_:)中用if语句表示

//Vertical Line
 
if isAddButton {
  //vertical line code moveToPoint(_:) and addLineToPoint(_:)
}
//existing code
//set the stroke color
UIColor.whiteColor().setStroke()
 
//draw the stroke
plusPath.stroke()

这些代码让你只有在isAddButton被设置的时候才能够画垂直线,用这样的方式,一个按钮就可以既是加法按钮,又可以是减法按钮。

完整的PushButtonView如下列所示:

import UIKit
 
@IBDesignable
class PushButtonView: UIButton {
 
  @IBInspectable var fillColor: UIColor = UIColor.greenColor()
  @IBInspectable var isAddButton: Bool = true
 
  override func drawRect(rect: CGRect) {
 
    var path = UIBezierPath(ovalInRect: rect)
    fillColor.setFill()
    path.fill()
 
    //set up the width and height variables
    //for the horizontal stroke
    let plusHeight: CGFloat = 3.0
    let plusWidth: CGFloat = min(bounds.width, bounds.height) * 0.6     
 
    //create the path
    var plusPath = UIBezierPath()
 
    //set the path's line width to the height of the stroke
    plusPath.lineWidth = plusHeight
 
    //move the initial point of the path
    //to the start of the horizontal stroke
    plusPath.moveToPoint(CGPoint(
      x:bounds.width/2 - plusWidth/2 + 0.5,
      y:bounds.height/2 + 0.5))
 
    //add a point to the path at the end of the stroke
    plusPath.addLineToPoint(CGPoint(
      x:bounds.width/2 + plusWidth/2 + 0.5,
      y:bounds.height/2 + 0.5))
 
    //Vertical Line
    if isAddButton {
      //move to the start of the vertical stroke
      plusPath.moveToPoint(CGPoint(
        x:bounds.width/2 + 0.5,
        y:bounds.height/2 - plusWidth/2 + 0.5))
 
      //add the end point to the vertical stroke
      plusPath.addLineToPoint(CGPoint(
        x:bounds.width/2 + 0.5,
        y:bounds.height/2 + plusWidth/2 + 0.5))
    }
 
    //set the stroke color
    UIColor.whiteColor().setStroke()
 
    //draw the stroke
    plusPath.stroke()
 
  }
 
}

在storyboard中,选择push button view.你可以看到两个通过@IBInspectable声明的属性出现在Attributes Inspector的顶部。

更改填充颜色为RGB(87, 218, 213),并且设置Is Add Button为off。

1

你可以立即在storyboard中看到相应的变化。

1

很酷吧?现在把Is Add Button设为on来把按钮设为加号。

第二个按钮

添加一个UIButton到StoryBoard,选中,并在尺寸查看器(size inspector)中更新按钮的位置和大小,这里设置为X=275,Y=480,Width=50,以及Height=50:

1

在Identity Inspector中,选择类型(Class)的下拉菜单,将UIButton更改为PushButtonView。

1

一个绿色的plus button功能按钮就会覆盖掉之前的按钮。
在Attributes inspector中,将填充颜色设置为RGB(238,77,77),并将 Is Add Button设置为off,将default title button取消。

1

按照之前的步骤,给新的视图添加自动布局的约束条件。

  • 选中按钮,按住”Ctrl”键鼠标拖动按钮的中间位置向左(拖动范围在按钮以内),在弹出菜单中选择Width。

  • 相似的,在按钮选中状态下,按住”Ctrl”键鼠标拖动按钮的中间位置向上(拖动范围在按钮以内),在弹出菜单中选择Height。

  • 按住”Ctrl”键鼠标向上拖动,从按钮里面拖动到按钮范围外,选择Center Horizontally in Containner。

  • 按住”Ctrl”键,鼠标从最底部的按钮拖动到顶部按钮,可以选择Vertical Spacing。

编译并运行应用,你就可以获得一个可复用的自定义视图,并可以添加到任意app中。并且它还可以适用于任意设备的屏幕尺寸。这里给大家展示的是在iPhone 4S上面的效果。

1

用UIBezierPath来画弧

下一个你要自定义的View就是下面这个:

1-CompletedCounterView

这个看起来像是一个被填满的弧形,但是这个弧确确实实仅仅是一个粗笔画路径。而轮廓又是另一个由两个弧组成的粗笔画路径。

新建一个文件,File\New\File…,选择Cocoa Touch Class,然后命名这个类为CounterView。同时,让它为UIView的一个子类,并确认一下编程语言选择的是Swift。点击Next按钮,然后点击创建。然后用下面的代码把生成的代码覆盖掉:

import UIKit

let NoOfGlasses = 8
let π:CGFloat = CGFloat(M_PI)

@IBDesignable class CounterView: UIView {

    @IBInspectable var counter: Int = 5 
    @IBInspectable var outlineColor: UIColor = UIColor.blueColor()
    @IBInspectable var counterColor: UIColor = UIColor.orangeColor()

    override func drawRect(rect: CGRect) {

    }
}

注意:既然Apple允许在常量(let)和变量(var)中使用Unicode编码的字符,你可以把π作为一个常量的名字,用它来储存pi,这样能提高代码的可读性。如何输入π:同时按下Alt和P即可。

在这个代码中,你创建了两个常量。NoOfGlasses 代表着每天需要喝水的目标杯数。当计数到达了这个目标杯数的时候,这个计数器就到了它的最大值。
同时你也创建了3个你可以在storyboard里修改的@IBInspectable属性。这个counter变量用来记录当前已经喝的杯数。对于这个变量,能在storyboard里面修改它是很有必要的,尤其是当测试这个CounterView的时候。

打开Main.storyboard然后在“加号”按钮上添加一个UIView

在右边的Size Insepector面板里,设置X=185, Y=70, Width=230, 还有 Height=230:

1-CounterViewCoords

给这个新的view添加几个自动布局的约束,就像你之前做的那样:

  • 选中这个View,按住control从这个view(原文这里是button,应该是view的)的中心轻轻的向左边拖动鼠标,然后释放(仍然在这个view内),从弹出来的菜单中选择Width

  • 同理,选中这个View,按住control从这个view的中心轻轻的向上面拖动鼠标,然后释放(仍然在这个view内),从弹出来的菜单中选择Height

  • 按住control然后从这个view中拖动鼠标到外面的view然后释放,选择Center Horizontally in Container。

  • 按住control然后从这个view往下拖动鼠标,一直到上面一个button然后释放,选择Vertical Spacing.

在右边的Identity Inspector面板中,把这个UIView所属的类改成CounterView。现在你在drawRect(_:)中的代码将会生效。

温习数学知识

我们稍微打断一下这个教程,然后我们回顾一下高中级别的数学知识,千万不要害怕哦!就像Douglas Adams所说 – 不要慌张! :]

在这个context里画任何东西,都基于这个单元圆。一个单元圆就是半径为1的圆。

1-FloUnitCircle

红色的箭头表示你的弧从哪里开始和结束,并且是按顺时针方向。接下来,你将从 3π/4弧度(135º)的位置开始画弧,然后顺时针到 π/4弧度(45º)

在程序中,弧度经常被使用,而不是用度数。用弧度是非常有利的,因为你不必每次跟圆打交道的时候都要转换为度数。接下来,你需要计算一下这个弧的长度,当然是用弧度来算。

在单元圆(半径为1)中,弧的长度等于这个角度的用弧度表示的数值。比如说,看上面的哪个图,从0º到90º的弧的长度是 π/2。在真实场景中计算弧的长度,用单位圆的弧的长度乘以真实的圆的半径。

计算上面的红色箭头的长度,只要计算它所跨过的弧度数值。

      2π – end of arrow (3π/4) + point of arrow (π/4) = 3π/2

用角度表示就是:

      360º – 135º + 45º = 270º

celebrate-all-the-maths

让我们继续来画弧

// 1
let center = CGPoint(x:bounds.width/2, y: bounds.height/2)

// 2
let radius: CGFloat = max(bounds.width, bounds.height)

// 3
let arcWidth: CGFloat = 76

// 4
let startAngle: CGFloat = 3 * π / 4
let endAngle: CGFloat = π / 4

// 5
var path = UIBezierPath(arcCenter: center,
  radius: radius/2 - arcWidth/2,
  startAngle: startAngle,
  endAngle: endAngle,
  clockwise: true)

// 6
path.lineWidth = arcWidth
counterColor.setStroke()
path.stroke()

想象一下用圆规来画这个,首先把圆规的一个点固定在中间,然后打开圆规臂选择你需要的半径,然后装上一个细港币,然后旋转它来画弧。
在这个代码中,center变量代表着圆规的不动点,半径就是圆规打开的宽度(小于钢笔宽度的一半)并且这个弧的宽度就是这个钢笔笔头的宽度。

下面的列表解释了上面代码中每个小节的意思:

  1. 定义这个view的中点,然后你将会基于这个点来通过旋转生成弧。

  2. 通过view的宽和高的最大值来计算弧的半径。

  3. 定义这个弧的粗细

  4. 为这个弧定义开始和结束的角度。

  5. 基于刚刚创建的中点,半径和角度来定义个一个弧的轨道。

  6. 在给这个轨道上色之前,设置这个弧的轨道的宽度和颜色。

注意:当你在尝试画弧的时候,这些一般是你需要知道的,如果你想在画弧方面研究更深层次一点,你可以尝试阅读Raywenderlich老版本的 Core Graphics Tutorial on Arcs and Paths

在storyboard里或者是你运行你的应用的时候,下面这个就是你可以看到的

1-SimArcStroke

如何给弧加轮廓

当用户表示他们已经享受了一杯圣水之后(=。=),这个计数器外面的线条用来表示进度–离8杯还有多少。

这个外面的线由两个弧组成,一个外部一个内部,并且还有两条线来闭合他们。
在CounterView.swift里,把下面的代码加在drawRect(_:):的最后面:

//Draw the outline

//1 - first calculate the difference between the two angles
//ensuring it is positive
let angleDifference: CGFloat = 2 * π - startAngle + endAngle

//then calculate the arc for each single glass
let arcLengthPerGlass = angleDifference / CGFloat(NoOfGlasses)

//then multiply out by the actual glasses drunk
let outlineEndAngle = arcLengthPerGlass * CGFloat(counter) + startAngle

//2 - draw the outer arc
var outlinePath = UIBezierPath(arcCenter: center, 
                               radius: bounds.width/2 - 2.5,
                           startAngle: startAngle, 
                             endAngle: outlineEndAngle,
                            clockwise: true)

//3 - draw the inner arc
outlinePath.addArcWithCenter(center, 
                  radius: bounds.width/2 - arcWidth + 2.5,
              startAngle: outlineEndAngle, 
                endAngle: startAngle, 
               clockwise: false)

//4 - close the path
outlinePath.closePath()

outlineColor.setStroke()
outlinePath.lineWidth = 5.0
outlinePath.stroke()

下面解释一下各个小节代码的意思:

  1. oulineEndAngle表示这个弧应该结束的角度,用当前的counter的值来计算。

  2. outlinePath 就是外面的弧。由于这个弧不是基于单元圆的,所以把半径传给UIBezierPath()这个方法来计算这个弧的真实长度。

  3. 在第一个弧的基础上再加一个内部的弧。这个和前面一个弧的角度一样但是是反向的(clockwise的值置为false)。同时,这一段会自动生成一个直线连接内部弧还外部的弧。

  4. 在另一个弧的末尾自动画一条直线来关闭整个弧的路径。

因为你在CounterView.swift中将counter属性设置为5,你的CounterView现在应该在storyboard中看起来像下面这样:

1-ArcOutline

打开 Main.storyboard, 选择CounterView然后在右边的Attributes Insepector里,改变Counter的数值来检查你写在drawRect里的代码是否正确。然后你惊喜的发现这个storyboard里面也跟着改变了,它竟然是可以交互的。尝试改变counter的数值去超过8或者小于0。接下来你会修改这个。

1-CounterView

让一切都能够正常工作

祝贺你!你现在已经能够掌控这些了,现在你需要做的就是点击加号按钮来增加这个计数器的数值,或者是点击减号按钮来减少这个计数器的数值。

Main.storyboard中,拖拽一个UILable控件,放置在CounterView的中间。让它成为CounterView的子View。

然后在右边的Size Inspector中,设置X=93, Y=93, Width=44, 还有Height=44:

1-LabelCoords

在右边的Attributes Inspector,改变Alignment参数为center,font size为36并且默认的Label title为8.

1-LabelAttributes

打开ViewController.swift然后添加下面的属性到这个类的顶部:

//Counter outlets
@IBOutlet weak var counterView: CounterView!
@IBOutlet weak var counterLabel: UILabel!

还是在ViewController.swift里,添加下面的方法到这个类的最下面。

@IBAction func btnPushButton(button: PushButtonView) {
if button.isAddButton {
 counterView.counter++
} else {
 if counterView.counter > 0 {
   counterView.counter--
 }
}
counterLabel.text = String(counterView.counter)
}

现在,你对计数器的增加或者减少都依赖于按钮的isAddButton属性,确保计数器不会降到0以下-没人会喝负数杯数的水。:] 同时你也要在更新label上面的计数器数值。

把下面这段代码也加入到viewDidLoad()的底部,确保这个counterLabel的初始数值也被赋值到。

counterLabel.text = String(counterView.counter)

Main.storyboard中,连接CounterView outlet和UILabel outlet到ViewController中,同时连接两个按钮的Touch Up Inside事件到ViewController里面的相关方法上。

1-ConnectingOutlets2

运行这个应用,然后看看是否你的按钮能够更新这个label上面的数字,他们应该能行的。

等等!为什么这个CounterView上面的进度没有更新?

回想一下在这个教程的开始,drawRect(_:)方法只有在某些情况下会被调用,比如:遮住它的view被移开的时候,或者是它的hidden属性被改变的时候,或者是这个view第一次出现在屏幕上,或者是你的app调用了setNeedsDisplay()或者setNeedsDisplayInRect()方法。

然而,一旦counter属性改变的时候,CounterView也必须改变它的进度条。否则,用户就会认为你的app毫无用处。

打开CounterView.swift 然后改变这个counter属性声明为下面的代码:

@IBInspectable var counter: Int = 5 {
    didSet {
        if counter <=  NoOfGlasses {
        //the view needs to be refreshed
        setNeedsDisplay()
        }
    }
}

这段代码使CounterView仅仅在counter的数值小于或者等于用户的每天目标杯数的时候才会刷新,因为外面的轮廓只会最高到8.

再一次运行你的app,一切都正常工作起来。

1-Part1Finished

接下来何去何从?

在这个教程你已经学习到了基本的绘图,现在你应该能够在你的UI里改变你的view的形状。但是等等,还有很多需要学习!在这篇教程的第二部分,你将会尝试更深层次探索Core Graphics context并且创造一个记录你的喝水杯数的图表。

你可以下载这个项目的所有代码,如果你有任何问题和评论,在下面的讨论中提出来。


收藏 赞 (0) 踩 (0)
上一篇:行为驱动开发iOS
Designer News.png 前段时间在 design+code 购买了一个学习iOS设计和编码在线课程,使用Sketch设计App,然后使用Swift语言实现 Designer News 客户端。作者Meng To已经开源到 Github:MengTo/DesignerNewsApp · GitHub 。虽然实现整个Designer News客户端基
下一篇:Core Graphics教程第二部分(Swift) – Gradients 与 Context
原文链接 : Core Graphics Tutorial Part 2: Gradients and Contexts 原文作者 : caroline 译文出自 : 开发技术前线 译者 : HarriesChen 校对者: HarriesChen 更新时间 04/15/2015 为Xcode 6.3 和 Swift1.2更新 欢迎回到我们的Swift核心绘图教程系列! 在 第