Swift 全功能的绘图板开发

转载请注明出处:http://www.jb51.cc/article/p-htigfqxi-ber.html

要做一个全功能的绘图板,至少要支持以下这些功能

  • 支持铅笔绘图(画点)
  • 支持画直线
  • 支持一些简单的图形(矩形、圆形等)
  • 做一个真正的橡皮擦
  • 能设置画笔的粗细
  • 能设置画笔的颜色
  • 能设置背景色或者背景图
  • 支持撤消与重做

我们先做一些基础性的工作,比如创建工程。


工程搭建

先创建一个Single View Application工程:

语言选择Swift

为了最大程度的利用屏幕区域,我们完全隐藏掉状态栏,在Info.plist修改添加这两个参数:

然后进入到Main.storyboard,开始搭建我们的UI。
我们给已存在的ViewControllerView添加一个UIImageView的子视图,背景色设为Light Gray,然后添加4个约束,由于要做一个全屏的画板,必须要让Constraint to margins保持没有选中的状态,否则左右两边会留下苹果建议的空白区域,最后把User Interaction Enabled打开:


然后我们回到View上:

  • 添加一个放工具栏的容器:UIView,为该View设置约束:

    同样的不要选择Contraint to margins
  • 在该View里添加一个UISegmentedControl,并给SegmentedControl设置6个选项,分别是:

    1. 铅笔
    2. 直尺
    3. 虚线
    4. 矩形
    5. 圆形
    6. 橡皮擦
  • 给这个SegmentedControl添加约束:

    垂直居中,两边各留20,高度固定为28。

完整的UI及结构看起来像这样:

ImageView将会作为实际的绘制区域,顶部的SegmentedControl提供工具的选择。 到目前为止我们还没有写下一行代码,至此要开始编码了。

你可能会注意到Board有一部分被挡住了,这只是暂时的~


施工…

Board

我们创建一个Board类,继承自UIImageView,同时把这个类设置为Main.storyboardImageView的Class,这样当app启动的时候就会自动创建一个Board的实例了。
增加两个属性以及初始化方法

<code class="language-swift hljs objectivec has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; Box-sizing: border-Box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">var strokeWidth: <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">CGFloat</span>
var strokeColor: <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">UIColor</span>

override init() {
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">self</span><span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">.strokeColor</span> = <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">UIColor</span><span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">.blackColor</span>()
    <span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.strokeWidth</span> = <span class="hljs-number" style="color: rgb(0,102,102); Box-sizing: border-Box;">1</span>

    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">super</span><span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">.init</span>()
}

required init(coder aDecoder: NSCoder) {
    <span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.init</span>(coder: aDecoder)
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,238,238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,221,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li></ul>

由于我们是依赖于touches方法来完成绘图过程,我们需要记录下每次touch的状态,比如beganmovedended等,为此我们创建一个枚举,在touches方法中进行记录,并调用私有的绘图方法drawingImage

<code class="language-swift hljs cs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; Box-sizing: border-Box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">enum</span> DrawingState {
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">case</span> Began,Moved,Ended
}

class Board: UIImageView {

    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">private</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">var</span> drawingState: DrawingState!

    // 此处省略init方法与另外两个属性

    // MARK: - touches methods

    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">override</span> func <span class="hljs-title" style="Box-sizing: border-Box;">touchesBegan</span>(touches: NSSet,withEvent <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">event</span>: UIEvent) {
        self.drawingState = .Began
        self.drawingImage()
    }

    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">override</span> func touchesMoved(touches: NSSet,136); Box-sizing: border-Box;">event</span>: UIEvent) {
        self.drawingState = .Moved
        self.drawingImage()
    }

    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">override</span> func touchesEnded(touches: NSSet,136); Box-sizing: border-Box;">event</span>: UIEvent) {
        self.drawingState = .Ended
        self.drawingImage()
    }

    <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// MARK: - drawing</span>

    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">private</span> func <span class="hljs-title" style="Box-sizing: border-Box;">drawingImage</span>() {
        <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// 暂时为空实现</span>
    }
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li><li style="Box-sizing: border-Box; padding: 0px 5px;">21</li><li style="Box-sizing: border-Box; padding: 0px 5px;">22</li><li style="Box-sizing: border-Box; padding: 0px 5px;">23</li><li style="Box-sizing: border-Box; padding: 0px 5px;">24</li><li style="Box-sizing: border-Box; padding: 0px 5px;">25</li><li style="Box-sizing: border-Box; padding: 0px 5px;">26</li><li style="Box-sizing: border-Box; padding: 0px 5px;">27</li><li style="Box-sizing: border-Box; padding: 0px 5px;">28</li><li style="Box-sizing: border-Box; padding: 0px 5px;">29</li><li style="Box-sizing: border-Box; padding: 0px 5px;">30</li><li style="Box-sizing: border-Box; padding: 0px 5px;">31</li><li style="Box-sizing: border-Box; padding: 0px 5px;">32</li><li style="Box-sizing: border-Box; padding: 0px 5px;">33</li></ul>

在我们实现drawingImage方法之前,我们先创建另外一个重要的组件:BaseBrush

BaseBrush

顾名思义,BaseBrush将会作为一个绘图的基类而存在,我们会在它的基础上创建一系列的子类,以达到弹性的设计目的。为此,我们创建一个BaseBrush类,并实现一个PaintBrush接口:

<code class="language-swift hljs go has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; Box-sizing: border-Box; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">import</span> CoreGraphics

protocol PaintBrush {

    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">func</span> supportedContinuousDrawing() -> Bool;

    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">func</span> drawInContext(context: CGContextRef)
}

class BaseBrush : NSObject,PaintBrush {
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">var</span> beginPoint: CGPoint!
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">var</span> endPoint: CGPoint!
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">var</span> lastPoint: CGPoint?

    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">var</span> strokeWidth: CGFloat!

    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">func</span> supportedContinuousDrawing() -> Bool {
        <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> <span class="hljs-constant" style="Box-sizing: border-Box;">false</span>
    }

    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">func</span> drawInContext(context: CGContextRef) {
        assert(<span class="hljs-constant" style="Box-sizing: border-Box;">false</span>,<span class="hljs-string" style="color: rgb(0,136,0); Box-sizing: border-Box;">"must implements in subclass."</span>)
    }
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li><li style="Box-sizing: border-Box; padding: 0px 5px;">21</li><li style="Box-sizing: border-Box; padding: 0px 5px;">22</li><li style="Box-sizing: border-Box; padding: 0px 5px;">23</li><li style="Box-sizing: border-Box; padding: 0px 5px;">24</li></ul>

BaseBrush实现了PaintBrush接口,PaintBrush声明了两个方法

  • supportedContinuousDrawing,表示是否是连续不断的绘图
  • drawInContext,基于Context的绘图方法,子类必须实现具体的绘图

只要是实现了PaintBrush接口的类,我们就当作是一个绘图工具(如铅笔、直尺等),而BaseBrush除了实现PaintBrush接口以外,我们还为它增加了四个便利属性

  • beginPoint,开始点的位置
  • endPoint,结束点的位置
  • lastPoint,最后一个点的位置(也可以称作是上一个点的位置)
  • strokeWidth,画笔的宽度

这么一来,子类也可以很方便的获取到当前的状态,并作一些深度定制的绘图方法

lastPoint的意义:beginPoint和endPoint很好理解,beginPoint是手势刚识别时的点,只要手势不结束,那么beginPoint在手势识别期间是不会变的;endPoint总是表示手势最后识别的点;除了铅笔以外,其他的图形用这两个属性就够了,但是用铅笔在移动的时候,不能每次从beginPoint画到endPoint,如果是那样的话就是画直线了,而是应该从上一次画的位置(lastPoint)画到endPoint,这样才是连贯的线。

回到Board

我们实现了一个画笔的基类之后,就可以重新回到Board类了,毕竟我们之前的工作还没有做完,现在是时候完善Board类了。
我们用Board实际操纵BaseBrush,先为Board添加两个新的属性

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">var</span> brush: BaseBrush?

<span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">var</span> realImage: UIImage?    </code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li></ul>

brush对应到具体的画笔类,realImage保存当前的图形,重新修改touches方法,以便增加brush属性的处理,完整的touches方法实现如下:

<code class="language-swift hljs avrasm has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; Box-sizing: border-Box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">// MARK: - touches methods

override func touchesBegan(touches: NSSet,withEvent event: UIEvent) {
    if let brush = self<span class="hljs-preprocessor" style="color: rgb(68,68,68); Box-sizing: border-Box;">.brush</span> {
        brush<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.lastPoint</span> = nil

        brush<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.beginPoint</span> = touches<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.anyObject</span>()!<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.locationInView</span>(self)
        brush<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.endPoint</span> = brush<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.beginPoint</span>

        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.drawingState</span> = <span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.Began</span>
        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.drawingImage</span>()
    }
}

override func touchesMoved(touches: NSSet,68); Box-sizing: border-Box;">.endPoint</span> = touches<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.locationInView</span>(self)

        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.Moved</span>
        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.drawingImage</span>()
    }
}

override func touchesCancelled(touches: NSSet!,withEvent event: UIEvent!) {
    if let brush = self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.endPoint</span> = nil
    }
}

override func touchesEnded(touches: NSSet,68); Box-sizing: border-Box;">.Ended</span>

        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.drawingImage</span>()
    }
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li><li style="Box-sizing: border-Box; padding: 0px 5px;">21</li><li style="Box-sizing: border-Box; padding: 0px 5px;">22</li><li style="Box-sizing: border-Box; padding: 0px 5px;">23</li><li style="Box-sizing: border-Box; padding: 0px 5px;">24</li><li style="Box-sizing: border-Box; padding: 0px 5px;">25</li><li style="Box-sizing: border-Box; padding: 0px 5px;">26</li><li style="Box-sizing: border-Box; padding: 0px 5px;">27</li><li style="Box-sizing: border-Box; padding: 0px 5px;">28</li><li style="Box-sizing: border-Box; padding: 0px 5px;">29</li><li style="Box-sizing: border-Box; padding: 0px 5px;">30</li><li style="Box-sizing: border-Box; padding: 0px 5px;">31</li><li style="Box-sizing: border-Box; padding: 0px 5px;">32</li><li style="Box-sizing: border-Box; padding: 0px 5px;">33</li><li style="Box-sizing: border-Box; padding: 0px 5px;">34</li><li style="Box-sizing: border-Box; padding: 0px 5px;">35</li><li style="Box-sizing: border-Box; padding: 0px 5px;">36</li><li style="Box-sizing: border-Box; padding: 0px 5px;">37</li><li style="Box-sizing: border-Box; padding: 0px 5px;">38</li></ul>

我们需要防止brushnil的情况,以及为brush设置好beginPointendPoint,之后我们就可以完善drawingImage方法了,实现如下:

<code class="language-swift hljs lasso has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; Box-sizing: border-Box; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">private</span> func drawingImage() {
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">let</span> brush <span class="hljs-subst" style="color: rgb(0,0); Box-sizing: border-Box;">=</span> <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">self</span><span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>brush {

        <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// 1.</span>
        UIGraphicsBeginImageContext(<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>bounds<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>size)

        <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// 2.</span>
        <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">let</span> context <span class="hljs-subst" style="color: rgb(0,0); Box-sizing: border-Box;">=</span> UIGraphicsGetCurrentContext()

        UIColor<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>clearColor()<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>setFill()
        UIRectFill(<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>bounds)

        CGContextSetLineCap(context,kCGLineCapRound)
        CGContextSetLineWidth(context,<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>strokeWidth)
        CGContextSetStrokeColorWithColor(context,102); Box-sizing: border-Box;">.</span>strokeColor<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>CGColor)

        <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// 3.</span>
        <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">let</span> realImage <span class="hljs-subst" style="color: rgb(0,102); Box-sizing: border-Box;">.</span>realImage {
            realImage<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>drawInRect(<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>bounds)
        }

        <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// 4.</span>
        brush<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>strokeWidth <span class="hljs-subst" style="color: rgb(0,102); Box-sizing: border-Box;">.</span>strokeWidth
        brush<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>drawInContext(context);
        CGContextStrokePath(context)

        <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// 5.</span>
        <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">let</span> previewImage <span class="hljs-subst" style="color: rgb(0,0); Box-sizing: border-Box;">=</span> UIGraphicsGetImageFromCurrentImageContext()
        <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>drawingState <span class="hljs-subst" style="color: rgb(0,0); Box-sizing: border-Box;">==</span> <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>Ended <span class="hljs-subst" style="color: rgb(0,0); Box-sizing: border-Box;">||</span> brush<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>supportedContinuousDrawing() {
            <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>realImage <span class="hljs-subst" style="color: rgb(0,0); Box-sizing: border-Box;">=</span> previewImage
        }

        UIGraphicsEndImageContext()

        <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// 6.</span>
        <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>image <span class="hljs-subst" style="color: rgb(0,0); Box-sizing: border-Box;">=</span> previewImage;

        brush<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>lastPoint <span class="hljs-subst" style="color: rgb(0,0); Box-sizing: border-Box;">=</span> brush<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>endPoint
    }
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li><li style="Box-sizing: border-Box; padding: 0px 5px;">21</li><li style="Box-sizing: border-Box; padding: 0px 5px;">22</li><li style="Box-sizing: border-Box; padding: 0px 5px;">23</li><li style="Box-sizing: border-Box; padding: 0px 5px;">24</li><li style="Box-sizing: border-Box; padding: 0px 5px;">25</li><li style="Box-sizing: border-Box; padding: 0px 5px;">26</li><li style="Box-sizing: border-Box; padding: 0px 5px;">27</li><li style="Box-sizing: border-Box; padding: 0px 5px;">28</li><li style="Box-sizing: border-Box; padding: 0px 5px;">29</li><li style="Box-sizing: border-Box; padding: 0px 5px;">30</li><li style="Box-sizing: border-Box; padding: 0px 5px;">31</li><li style="Box-sizing: border-Box; padding: 0px 5px;">32</li><li style="Box-sizing: border-Box; padding: 0px 5px;">33</li><li style="Box-sizing: border-Box; padding: 0px 5px;">34</li><li style="Box-sizing: border-Box; padding: 0px 5px;">35</li><li style="Box-sizing: border-Box; padding: 0px 5px;">36</li><li style="Box-sizing: border-Box; padding: 0px 5px;">37</li><li style="Box-sizing: border-Box; padding: 0px 5px;">38</li><li style="Box-sizing: border-Box; padding: 0px 5px;">39</li><li style="Box-sizing: border-Box; padding: 0px 5px;">40</li></ul>

步骤解析:

  1. 开启一个新的ImageContext,为保存每次的绘图状态作准备。
  2. 初始化context,进行基本设置(画笔宽度、画笔颜色、画笔的圆润度等)。
  3. 把之前保存的图片绘制进context中。
  4. 设置brush的基本属性,以便子类更方便的绘图;调用具体的绘图方法,并最终添加到context中。
  5. 从当前的context中,得到Image,如果是ended状态或者需要支持连续不断的绘图,则将Image保存到realImage中。
  6. 实时显示当前的绘制状态,并记录绘制的最后一个点。

这些工作完成以后,我们就可以开始写第一个工具了:铅笔工具。

铅笔工具

铅笔工具应该支持连续不断的绘图(不断的保存到realImage中),这也是我们给PaintBrush接口增加supportedContinuousDrawing方法的原因,考虑到用户的手指可能快速的移动,导致从一个点到另一个点有着跳跃性的动作,我们对铅笔工具采用画直线的方式来实现。
首先创建一个类,名为PencilBrush,继承自BaseBrush类,实现如下:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">class PencilBrush: BaseBrush {

    override func drawInContext(context: CGContextRef) {
        if let lastPoint = self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.lastPoint</span> {
            CGContextMoveToPoint(context,lastPoint<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.x</span>,68); Box-sizing: border-Box;">.y</span>)
            CGContextAddLineToPoint(context,endPoint<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.y</span>)
        } else {
            CGContextMoveToPoint(context,beginPoint<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.y</span>)
        }
    }

    override func supportedContinuousDrawing() -> Bool {
        return true
    }
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li></ul>

如果lastPoint为nil,则基于beginPoint画线,反之则基于lastPoint画线。
这样一来,一个铅笔工具就完成了,怎么样,很简单吧。


测试

到目前为止,我们的ViewController还保持着默认的状态,是时候先为铅笔工具写一些测试代码了。
ViewController添加board属性,并与Main.storyboard中的Board关联起来;创建一个brushes属性,并为之赋值为:

<code class="language-swift hljs fix has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; Box-sizing: border-Box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-attribute" style="Box-sizing: border-Box;">var brushes </span>=<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;"> [PencilBrush()]</span></code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li></ul>

ViewController添加switchBrush:方法,并把Main.storyboard中的SegmentedControl的ValueChanged连接到switchBrush:方法上,实现如下:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">@IBAction func switchBrush(sender: UISegmentedControl) {
    assert(sender<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.tag</span> < self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.brushes</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.count</span>,0); Box-sizing: border-Box;">"!!!"</span>)

    self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.board</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.brush</span> = self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.brushes</span>[sender<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.selectedSegmentIndex</span>]
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li></ul>

最后在viewDidLoad方法中做一个初始化:
self.board.brush = brushes[0]
编译、运行,铅笔工具可以完美运行~!


其他的工具

接下来我们把其他的绘图工具也实现了。
其他的工具不像铅笔工具,不需要支持连续不断的绘图,所以也就不用覆盖supportedContinuousDrawing方法了。

直尺

创建一个LineBrush类,实现如下:

<code class="language-swift hljs haskell has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; Box-sizing: border-Box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-class" style="Box-sizing: border-Box;"><span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">class</span> <span class="hljs-type" style="Box-sizing: border-Box; color: rgb(102,102);">LineBrush</span>: <span class="hljs-type" style="Box-sizing: border-Box; color: rgb(102,102);">BaseBrush</span> {

    override func drawInContext<span class="hljs-container" style="Box-sizing: border-Box;">(<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">context</span>: <span class="hljs-type" style="Box-sizing: border-Box; color: rgb(102,102);">CGContextRef</span>)</span> {
        <span class="hljs-type" style="Box-sizing: border-Box; color: rgb(102,102);">CGContextMoveToPoint</span><span class="hljs-container" style="Box-sizing: border-Box;">(<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">context</span>,<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">beginPoint</span>.<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">x</span>,102);">y</span>)</span>
        <span class="hljs-type" style="Box-sizing: border-Box; color: rgb(102,102);">CGContextAddLineToPoint</span><span class="hljs-container" style="Box-sizing: border-Box;">(<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">endPoint</span>.<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">y</span>)</span>
    }
}</span></code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li></ul>

虚线

创建一个DashLineBrush类,实现如下:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',102);">DashLineBrush</span>: <span class="hljs-type" style="Box-sizing: border-Box; color: rgb(102,102);">CGContextRef</span>)</span> {
        let lengths: [<span class="hljs-type" style="Box-sizing: border-Box; color: rgb(102,102);">CGFloat</span>] = [self.strokeWidth * 3,self.strokeWidth * 3]
        <span class="hljs-type" style="Box-sizing: border-Box; color: rgb(102,102);">CGContextSetLineDash</span><span class="hljs-container" style="Box-sizing: border-Box;">(<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">lengths</span>,2)</span>;

        <span class="hljs-type" style="Box-sizing: border-Box; color: rgb(102,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li></ul>

这里我们就用到了BaseBrushstrokeWidth属性,因为我们想要创建一条动态的虚线。

矩形

创建一个RectangleBrush类,实现如下:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',102);">RectangleBrush</span>: <span class="hljs-type" style="Box-sizing: border-Box; color: rgb(102,102);">CGContextAddRect</span><span class="hljs-container" style="Box-sizing: border-Box;">(<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,<span class="hljs-type" style="Box-sizing: border-Box; color: rgb(102,102);">CGRect</span>(<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">origin</span>: <span class="hljs-type" style="Box-sizing: border-Box; color: rgb(102,102);">CGPoint</span>(<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">x</span>: <span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">min</span>(<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">x</span>)</span>,y: min<span class="hljs-container" style="Box-sizing: border-Box;">(<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">y</span>,102);">y</span>)</span>),size: <span class="hljs-type" style="Box-sizing: border-Box; color: rgb(102,102);">CGSize</span><span class="hljs-container" style="Box-sizing: border-Box;">(<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">width</span>: <span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">abs</span>(<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">x</span> - <span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,height: abs<span class="hljs-container" style="Box-sizing: border-Box;">(<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">y</span> - <span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">y</span>)</span>)))
    }
}</span></code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li></ul>

我们用到了一些计算,因为我们希望矩形的区域不是由beginPoint定死的。

圆形

创建一个EllipseBrush类,实现如下:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',102);">EllipseBrush</span>: <span class="hljs-type" style="Box-sizing: border-Box; color: rgb(102,102);">CGContextAddEllipseInRect</span><span class="hljs-container" style="Box-sizing: border-Box;">(<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li></ul>

同样有一些计算,理由同上。

橡皮擦

从本文一开始就说过了,我们要做一个真正的橡皮擦,网上有很多的橡皮擦的实现其实就是把画笔颜色设置为背景色,但是如果背景色可以动态设置,甚至设置为一个渐变的图片时,这种方法就失效了,所以有些绘图app的背景色就是固定为白色的。
其实Apple的Quartz2D框架本身就是支持橡皮擦的,只用一个方法就可以完美实现。
让我们创建一个EraserBrush类,实现如下:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',102);">EraserBrush</span>: <span class="hljs-type" style="Box-sizing: border-Box; color: rgb(102,102);">PencilBrush</span> {

    override func drawInContext<span class="hljs-container" style="Box-sizing: border-Box;">(<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">CGContextSetBlendMode</span><span class="hljs-container" style="Box-sizing: border-Box;">(<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">kCGBlendModeClear</span>)</span>;

        super.drawInContext<span class="hljs-container" style="Box-sizing: border-Box;">(<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">context</span>)</span>
    }
}</span></code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li></ul>

注意,与其他的工具不同,橡皮擦是继承自PencilBrush的,因为橡皮擦本身也是基于点的,而drawInContext里也只是加了一句:

<code class="language-swift hljs vhdl has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; Box-sizing: border-Box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">CGContextSetBlendMode(<span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">context</span>,kCGBlendModeClear);</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li></ul>

加入这一句代码,一个真正的橡皮擦便实现了。


再次测试

现在我们的工程结构应该类似于这样:

我们修改ViewController中的brushes属性的初始值:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',0); Box-sizing: border-Box;"> [PencilBrush(),LineBrush(),DashLineBrush(),RectangleBrush(),EllipseBrush(),EraserBrush()]</span></code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li></ul>

编译、运行:

除了橡皮擦擦除的范围太小以外,一切都很完美~!


设计思路

在继续完成剩下的功能之前,我想先对之前的代码进行些说明。

为什么不用drawRect方法

其实我最开始也是使用drawRect方法来完成绘制,但是感觉限制很多,比如context无法保存,还是要每次重画(虽然可以保存到一个BitMapContext里,但是这样与保存到image里有什么区别呢?);后来用CALayer保存每一条CGPath,但是这样仍然不能避免每次重绘,因为需要考虑到橡皮擦和画笔属性之类的影响,这么一来还不如采用image的方式来保存最新绘图板。
既然定下了以image来保存绘图板,那么drawRect就不方便了,因为不能用UIGraphicsBeginImageContext方法来创建一个ImageContext。

ViewController与Board、BaseBrush之间的关系

ViewControllerBoardBaseBrush这三者之间,虽然VC要知道另外两个组件,但是仅限于选择对应的工具给Board,Board本身并不知道当前的brush是哪个brush,也不需要知道其内部实现,只管调用对应的brush就行了;BaseBrush(及其子类)也并不知道自己将会被用于哪,它们只需要实现自己的算法即可。类似于这样的图:

实际上这里包含了两个设计模式。

策略设计模式

策略设计模式的UML图:

策略设计模式在iOS中也应用广泛,如AFNetworkingAFHTTPRequestSerializerAFHTTPResponseSerializer的设计,通过在运行时动态的改变委托对象,变换行为,使程序模块之间解耦、提高应变能力。
以我们的绘图板为例,输出不同的图形就意味着不同的算法,用户可根据不同的需求来选择某一种算法,即BaseBrush及其子类做具体的封装,这样的好处是每一个子类只关心自己的算法,达到了高聚合的原则,高级模块(Board)不用关心具体实现。
想象一下,如果是让Board里自身来处理这些算法,那代码中无疑会充斥很多与算法选择相关的逻辑,而且每增加一个算法都需要重新修改Board类,这又与代码应该对拓展开放、对修改关闭原则有了冲突,而且每个类也应该只有一个责任。
通过采用策略模式我们实现了一个好维护、易拓展的程序(妈妈再也不用担心工具栏不够用了^^)。

策略模式的定义:定义一个算法群,把每一个算法分别封装起来,让它们之间可以互相替换,使算法的变化独立于使用它的用户之上。

模板方法

在传统的策略模式中,每一个算法类都独自完成整个算法过程,例如一个网络解析程序,可能有一个算法用于解析JSON,有另一个算法用于解析XML等(另外一个例子是压缩程序,用ZIPRAR算法),独自完成整个算法对灵活性更好,但免不了会有重复代码,在DrawingBoard里我们做一个折中,尽量保证灵活性,又最大限度地避免重复代码
我们将BaseBrush的角色提升为算法的基类,并提供一些便利的属性(如beginPointendPointstrokeWidth等),然后在BoarddrawingImage方法里对BaseBrush的接口进行调用,而BaseBrush不会知道自己的接口是如何联系起来的,虽然supportedContinuousDrawing(这是一个“钩子”)甚至影响了算法的流程(铅笔需要实时绘图)。
我们用drawingImage搭建了一个算法的骨架,看起来像是模板方法的UML图:

图中右边的方框代表模板方法

BaseBrush通过提供抽象方法(drawInContext)、具体方法或钩子方法(supportedContinuousDrawing)来对应算法的每一个步骤,让其子类可以重定义或实现这些步骤。同时,让模板方法(即dawingImage)定义一个算法的骨架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法
除了对算法的封装以外,模板方法还能防止“循环依赖”,即高层组件依赖低层组件,反过来低层组件也依赖高层组件。想像一下,如果既让Board选择具体的算法子类,又让算法类直接调用drawingImage方法(不提供钩子,直接把Board的事件下发下去),那到时候就热闹了,这些类串在一起难以理解,又不好维护。

模板方法的定义:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

其实模式都很简单,很多人在工作中会思考如何让自己的代码变得更好,“情不自禁”地就会慢慢实现这些原则,了解模式的设计意图,有助于在遇到需要折中的地方更加明白如何在设计上取舍。

以上就是我设计时的思路,说完了,接下来还要完成的工作有:

  • 提供对画笔颜色、粗细的设置
  • 背景设置
  • 全屏绘图(不能让Board一直显示不全)

先从画笔开始,Let’s go!


画笔设置

不管是画笔还是背景设置,我们都要有一个能提供设置的工具栏。

设置工具栏

所以我们往Board上再盖一个UIToolbar,与顶部的View类似:

  1. 拖一个UIToolbarBoard父类上,与Board的视图层级平级。
  2. 设置UIToolbar的约束:左、右、下间距为0,高为44:
  3. UIToolbar上拖一个UIBarButtonItemtitle就写:画笔设置。
  4. ViewController增加一个paintingBrushSettings方法,并把UIBarButtonItemaction连接paintingBrushSettings方法上。
  5. toolar属性,并把Xib中的UIToolbar连接到toolbar上。

UIToolbar配置好后,UI及视图层级如下:

RGBColorPicker

考虑到多个页面需要选取自定义的颜色,我们先创建一个工具类:RGBColorPicker,用于选择RGB颜色:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">class RGBColorPicker: UIView {

    var colorChangedBlock: ((color: UIColor) -> Void)?

    private var sliders = [UiSlider]()
    private var labels = [UILabel]()

    override init(frame: CGRect) {
        super<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.init</span>(frame: frame)

        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.initial</span>()
    }

    required init(coder aDecoder: NSCoder) {
        super<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.init</span>(coder: aDecoder)

        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.initial</span>()
    }

    private func initial() {
        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.backgroundColor</span> = UIColor<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.clearColor</span>()
        let trackColors = [UIColor<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.redColor</span>(),UIColor<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.greenColor</span>(),68); Box-sizing: border-Box;">.blueColor</span>()]

        for index <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">in</span> <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">1.</span>.<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">.3</span> {
            let slider = UiSlider()
            slider<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.minimumValue</span> = <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">0</span>
            slider<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.value</span> = <span class="hljs-number" style="color: rgb(0,68); Box-sizing: border-Box;">.maximumValue</span> = <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">255</span>
            slider<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.minimumTrackTintColor</span> = trackColors[index - <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">1</span>]
            slider<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.addTarget</span>(self,action: <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"colorChanged:"</span>,forControlEvents: <span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.ValueChanged</span>)
            self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.addSubview</span>(slider)
            self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.sliders</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.append</span>(slider)

            let label = UILabel()
            label<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.text</span> = <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"0"</span>
            self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.addSubview</span>(label)
            self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.labels</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.append</span>(label)
        }
    }

    override func layoutSubviews() {
        super<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.layoutSubviews</span>()

        let sliderHeight = CGFloat(<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">31</span>)
        let labelWidth = CGFloat(<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">29</span>)
        let yHeight = self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.bounds</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.size</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.height</span> / CGFloat(sliders<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.count</span>)

        for index <span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">0.</span>.<self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.count</span> {
            let slider = self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.sliders</span>[index]
            slider<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.frame</span> = CGRect(<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">x</span>: <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">0</span>,102); Box-sizing: border-Box;">y</span>: CGFloat(index) * yHeight,width: self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.width</span> - labelWidth - <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">5.0</span>,height: sliderHeight)

            let label = self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.labels</span>[index]
            label<span class="hljs-preprocessor" style="color: rgb(68,102); Box-sizing: border-Box;">x</span>: CGRectGetMaxX(slider<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.frame</span>) + <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">5</span>,102); Box-sizing: border-Box;">y</span>: slider<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.frame</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.origin</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.y</span>,width: labelWidth,height: sliderHeight)
        }
    }

    override func intrinsicContentSize() -> CGSize {
        return CGSize(width: UIViewNoIntrinsicMetric,height: <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">107</span>)
    }

    @IBAction private func colorChanged(slider: UiSlider) {
        let color = UIColor(
            red: CGFloat(self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.sliders</span>[<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">0</span>]<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.value</span> / <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">255.0</span>),green: CGFloat(self<span class="hljs-preprocessor" style="color: rgb(68,102); Box-sizing: border-Box;">1</span>]<span class="hljs-preprocessor" style="color: rgb(68,blue: CGFloat(self<span class="hljs-preprocessor" style="color: rgb(68,102); Box-sizing: border-Box;">2</span>]<span class="hljs-preprocessor" style="color: rgb(68,alpha: <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">1</span>)

        let label = self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.labels</span>[find(self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.sliders</span>,slider)!]
        label<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.text</span> = NSString(format: <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"%.0f"</span>,slider<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.value</span>)

        if let colorChangedBlock = self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.colorChangedBlock</span> {
            colorChangedBlock(color: color)
        }
    }

    func setCurrentColor(color: UIColor) {
        var red: CGFloat = <span class="hljs-number" style="color: rgb(0,green: CGFloat = <span class="hljs-number" style="color: rgb(0,blue: CGFloat = <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">0</span>
        color<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.getRed</span>(&red,green: &green,blue: &blue,alpha: nil)
        let colors = [red,green,blue]

        for index <span class="hljs-keyword" style="color: rgb(0,68); Box-sizing: border-Box;">.value</span> = Float(colors[index]) * <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">255</span>

            let label = self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.value</span>)
        }
    }
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li><li style="Box-sizing: border-Box; padding: 0px 5px;">21</li><li style="Box-sizing: border-Box; padding: 0px 5px;">22</li><li style="Box-sizing: border-Box; padding: 0px 5px;">23</li><li style="Box-sizing: border-Box; padding: 0px 5px;">24</li><li style="Box-sizing: border-Box; padding: 0px 5px;">25</li><li style="Box-sizing: border-Box; padding: 0px 5px;">26</li><li style="Box-sizing: border-Box; padding: 0px 5px;">27</li><li style="Box-sizing: border-Box; padding: 0px 5px;">28</li><li style="Box-sizing: border-Box; padding: 0px 5px;">29</li><li style="Box-sizing: border-Box; padding: 0px 5px;">30</li><li style="Box-sizing: border-Box; padding: 0px 5px;">31</li><li style="Box-sizing: border-Box; padding: 0px 5px;">32</li><li style="Box-sizing: border-Box; padding: 0px 5px;">33</li><li style="Box-sizing: border-Box; padding: 0px 5px;">34</li><li style="Box-sizing: border-Box; padding: 0px 5px;">35</li><li style="Box-sizing: border-Box; padding: 0px 5px;">36</li><li style="Box-sizing: border-Box; padding: 0px 5px;">37</li><li style="Box-sizing: border-Box; padding: 0px 5px;">38</li><li style="Box-sizing: border-Box; padding: 0px 5px;">39</li><li style="Box-sizing: border-Box; padding: 0px 5px;">40</li><li style="Box-sizing: border-Box; padding: 0px 5px;">41</li><li style="Box-sizing: border-Box; padding: 0px 5px;">42</li><li style="Box-sizing: border-Box; padding: 0px 5px;">43</li><li style="Box-sizing: border-Box; padding: 0px 5px;">44</li><li style="Box-sizing: border-Box; padding: 0px 5px;">45</li><li style="Box-sizing: border-Box; padding: 0px 5px;">46</li><li style="Box-sizing: border-Box; padding: 0px 5px;">47</li><li style="Box-sizing: border-Box; padding: 0px 5px;">48</li><li style="Box-sizing: border-Box; padding: 0px 5px;">49</li><li style="Box-sizing: border-Box; padding: 0px 5px;">50</li><li style="Box-sizing: border-Box; padding: 0px 5px;">51</li><li style="Box-sizing: border-Box; padding: 0px 5px;">52</li><li style="Box-sizing: border-Box; padding: 0px 5px;">53</li><li style="Box-sizing: border-Box; padding: 0px 5px;">54</li><li style="Box-sizing: border-Box; padding: 0px 5px;">55</li><li style="Box-sizing: border-Box; padding: 0px 5px;">56</li><li style="Box-sizing: border-Box; padding: 0px 5px;">57</li><li style="Box-sizing: border-Box; padding: 0px 5px;">58</li><li style="Box-sizing: border-Box; padding: 0px 5px;">59</li><li style="Box-sizing: border-Box; padding: 0px 5px;">60</li><li style="Box-sizing: border-Box; padding: 0px 5px;">61</li><li style="Box-sizing: border-Box; padding: 0px 5px;">62</li><li style="Box-sizing: border-Box; padding: 0px 5px;">63</li><li style="Box-sizing: border-Box; padding: 0px 5px;">64</li><li style="Box-sizing: border-Box; padding: 0px 5px;">65</li><li style="Box-sizing: border-Box; padding: 0px 5px;">66</li><li style="Box-sizing: border-Box; padding: 0px 5px;">67</li><li style="Box-sizing: border-Box; padding: 0px 5px;">68</li><li style="Box-sizing: border-Box; padding: 0px 5px;">69</li><li style="Box-sizing: border-Box; padding: 0px 5px;">70</li><li style="Box-sizing: border-Box; padding: 0px 5px;">71</li><li style="Box-sizing: border-Box; padding: 0px 5px;">72</li><li style="Box-sizing: border-Box; padding: 0px 5px;">73</li><li style="Box-sizing: border-Box; padding: 0px 5px;">74</li><li style="Box-sizing: border-Box; padding: 0px 5px;">75</li><li style="Box-sizing: border-Box; padding: 0px 5px;">76</li><li style="Box-sizing: border-Box; padding: 0px 5px;">77</li><li style="Box-sizing: border-Box; padding: 0px 5px;">78</li><li style="Box-sizing: border-Box; padding: 0px 5px;">79</li><li style="Box-sizing: border-Box; padding: 0px 5px;">80</li><li style="Box-sizing: border-Box; padding: 0px 5px;">81</li><li style="Box-sizing: border-Box; padding: 0px 5px;">82</li><li style="Box-sizing: border-Box; padding: 0px 5px;">83</li><li style="Box-sizing: border-Box; padding: 0px 5px;">84</li><li style="Box-sizing: border-Box; padding: 0px 5px;">85</li><li style="Box-sizing: border-Box; padding: 0px 5px;">86</li><li style="Box-sizing: border-Box; padding: 0px 5px;">87</li><li style="Box-sizing: border-Box; padding: 0px 5px;">88</li><li style="Box-sizing: border-Box; padding: 0px 5px;">89</li></ul>

这个工具类很简单,没有采用Auto Layout进行布局,因为layoutSubviews方法已经能很好的满足我们的需求了。当用户拖动任何一个UiSlider的时候,我们能实时的通过colorChangedBlock回调给外部。它能展现一个这样的视图:

不过虽然该工具类本身没有采用Auto Layout进行布局,但是它还是支持Auto Layout的,当它被添加到某个Auto Layout的视图中的时候,Auto Layout布局系统可以通过intrinsicContentSize知道该视图的尺寸信息。
最后它还有一个setCurrentColor方法从外部接收一个UIColor,可以用于初始化。

画笔设置的UI

我打算在用户点击画笔设置的时候,从底部弹出一个控制面板(就像系统的Control Center那样),所以我们还要有一个像这样的设置UI:

具体的,创建一个PaintingBrushSettingsView类,同时创建一个PaintingBrushSettingsView.xib文件,并把xib中view的Class设为PaintingBrushSettingsView,设置view的背景色为透明:

  1. 放置一个title为“画笔粗细”的UILabel,约束设为:宽度固定为68,高度固定为21,左和上边距为8。
  2. 放置一个title为“1”的UILabel,“1”与“画笔粗细”的垂直间距为10,宽度固定为10,高度固定为21,与superview的左边距为10。
  3. 放置一个UiSlider,用于调节画笔的粗细,与“1”的水平间距为5,并与“1”垂直居中,高度固定为30,宽度暂时不设,在PaintingBrushSettingsView添加strokeWidthSlider属性,与之连接起来。
  4. 放置一个title为“20”的UILabel,约束设为:宽度固定为20,高度固定为21,top与“1”相同,与superview的右间距为10。并把上一步中的UiSlider的右间距设为与“20”相隔5。
  5. 放置一个title为“画笔颜色”的UILabel,宽、高、left与“画笔粗细”相同,与上面UiSlider的垂直间距设为12。
  6. 放置一个UIView至“画笔颜色”下方(上图中被选中的那个UIView),宽度固定为50,高度固定为30,left与“画笔颜色”相同,并且与“画笔颜色”的垂直间距为5,在strokeColorPreview属性,与之连接起来。
  7. 放置一个UIView,把它的Class改为RGBColorPicker,约束设为:left与顶部的UiSlider相同,底部与superview的间距为0,右间距为10,与上一步中的UIView的垂直间距为5。

PaintingBrushSettingsView类的完整代码如下:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">class PaintingBrushSettingsView : UIView {

    var strokeWidthChangedBlock: ((strokeWidth: CGFloat) -> Void)?
    var strokeColorChangedBlock: ((strokeColor: UIColor) -> Void)?

    @IBOutlet private var strokeWidthSlider: UiSlider!
    @IBOutlet private var strokeColorPreview: UIView!
    @IBOutlet private var colorPicker: RGBColorPicker!

    override func awakeFromNib() {
        super<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.awakeFromNib</span>()

        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.strokeColorPreview</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.layer</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.borderColor</span> = UIColor<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.blackColor</span>()<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.CGColor</span>
        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.borderWidth</span> = <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">1</span>

        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.colorPicker</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.colorChangedBlock</span> = {
            [unowned self] (color: UIColor) <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">in</span>

            self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.backgroundColor</span> = color
            if let strokeColorChangedBlock = self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.strokeColorChangedBlock</span> {
                strokeColorChangedBlock(strokeColor: color)
            }
        }

        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.strokeWidthSlider</span><span class="hljs-preprocessor" style="color: rgb(68,0); Box-sizing: border-Box;">"strokeWidthChanged:"</span>,68); Box-sizing: border-Box;">.ValueChanged</span>)
    }

    func setBackgroundColor(color: UIColor) {
        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.backgroundColor</span> = color
        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.setCurrentColor</span>(color)
    }

    func strokeWidthChanged(slider: UiSlider) {
        if let strokeWidthChangedBlock = self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.strokeWidthChangedBlock</span> {
            strokeWidthChangedBlock(strokeWidth: CGFloat(slider<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.value</span>))
        }
    }
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li><li style="Box-sizing: border-Box; padding: 0px 5px;">21</li><li style="Box-sizing: border-Box; padding: 0px 5px;">22</li><li style="Box-sizing: border-Box; padding: 0px 5px;">23</li><li style="Box-sizing: border-Box; padding: 0px 5px;">24</li><li style="Box-sizing: border-Box; padding: 0px 5px;">25</li><li style="Box-sizing: border-Box; padding: 0px 5px;">26</li><li style="Box-sizing: border-Box; padding: 0px 5px;">27</li><li style="Box-sizing: border-Box; padding: 0px 5px;">28</li><li style="Box-sizing: border-Box; padding: 0px 5px;">29</li><li style="Box-sizing: border-Box; padding: 0px 5px;">30</li><li style="Box-sizing: border-Box; padding: 0px 5px;">31</li><li style="Box-sizing: border-Box; padding: 0px 5px;">32</li><li style="Box-sizing: border-Box; padding: 0px 5px;">33</li><li style="Box-sizing: border-Box; padding: 0px 5px;">34</li><li style="Box-sizing: border-Box; padding: 0px 5px;">35</li><li style="Box-sizing: border-Box; padding: 0px 5px;">36</li><li style="Box-sizing: border-Box; padding: 0px 5px;">37</li><li style="Box-sizing: border-Box; padding: 0px 5px;">38</li></ul>

strokeWidthChangedBlockstrokeColorChangedBlock两个Block用于给外部传递状态。setBackgroundColor用于初始化。

关于 Swift 1.2

在 Swift 1.2里,不能用setBackgroundColor方法了,具体的,见Xcode 6.3的发布文档:Xcode 6.3 Release Notes,下面是用didSet代替原有的setBackgroundColor方法:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">override var backgroundColor: UIColor? {
    didSet {
        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.backgroundColor</span> = self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.backgroundColor</span>
        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.setCurrentColor</span>(self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.backgroundColor</span>!)

        super<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.backgroundColor</span> = oldValue
    }
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li></ul>

实现毛玻璃效果

在把PaintingBrushSettingsView显示出来之前,我们要先想一想以何种方式展现比较好,众所周知Control Center是有毛玻璃效果的,我们也想要这样的效果,而且不用自己实现。那如何产生效果? 答案是用UIToolbar就行了。
UIToolbar本身就是带有毛玻璃效果的,只要你不设置背景色,并且translucent属性为true,“恰好”我们页面底部就有一个UIToolbar,我们把它拉高就可以插入展现PaintingBrushSettingsView了。
只要get到了这一点,毛玻璃效果就算实现了~~

测试画笔设置

我们在ViewController新增加几个属性

<code class="language-swift hljs scala has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; Box-sizing: border-Box; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">var</span> toolbarEditingItems: [UIBarButtonItem]?
<span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">var</span> currentSettingsView: UIView?

<span class="hljs-annotation" style="color: rgb(155,133,157); Box-sizing: border-Box;">@IBOutlet</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">var</span> toolbarConstraintHeight: NSLayoutConstraint!</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li></ul>

toolbarConstraintHeight连接到Main.storyboard中对应的约束上就行了。toolbarEditingItems能让我们在UIToolbar显示不同的items,本来还需要一个toolbarItems属性的,因为UIViewController类本身就自带,我们便不用单独新增。currentSettingsView是用来保存当前展示的哪个设置页面,考虑到我们后面会增加背景设置,这个属性还是有必要的。
我们先写一个往toolbar上添加约束的工具方法

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">func addConstraintsToToolbarForSettingsView(view: UIView) {
    view<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.setTranslatesAutoresizingMaskIntoConstraints</span>(false)

    self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.toolbar</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.addSubview</span>(view)
    self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.addConstraints</span>(NSLayoutConstraint<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.constraintsWithVisualFormat</span>(<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"H:|-0-[settingsView]-0-|"</span>,options: <span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.DirectionLeadingToTrailing</span>,metrics: nil,views: [<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"settingsView"</span> : view]))
    self<span class="hljs-preprocessor" style="color: rgb(68,0); Box-sizing: border-Box;">"V:|-0-[settingsView(==height)]"</span>,metrics: [<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"height"</span> : view<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.systemLayoutSizeFittingSize</span>(UILayoutFittingCompressedSize)<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.height</span>],0); Box-sizing: border-Box;">"settingsView"</span> : view]))
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li></ul>

这个工具方法会把传入进来的view添加到toolbar上,同时添加相应的约束。注意高度的约束,我是通过systemLayoutSizeFittingSize方法计算出设置视图最佳的高度,这是为了达到更好的拓展性(背景设置与画笔设置所需要的高度很可能会不同)。
然后再增加一个setupBrushSettingsView方法

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">func setupBrushSettingsView() {
    let brushSettingsView = UINib(nibName: <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"PaintingBrushSettingsView"</span>,bundle: nil)<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.instantiateWithOwner</span>(nil,options: nil)<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.first</span> as PaintingBrushSettingsView

    self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.addConstraintsToToolbarForSettingsView</span>(brushSettingsView)

    brushSettingsView<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.hidden</span> = true
    brushSettingsView<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.tag</span> = <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">1</span>
    brushSettingsView<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.setBackgroundColor</span>(self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.strokeColor</span>)

    brushSettingsView<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.strokeWidthChangedBlock</span> = {
        [unowned self] (strokeWidth: CGFloat) -> Void <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">in</span>
        self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.strokeWidth</span> = strokeWidth
    }

    brushSettingsView<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.strokeColorChangedBlock</span> = {
        [unowned self] (strokeColor: UIColor) -> Void <span class="hljs-keyword" style="color: rgb(0,68); Box-sizing: border-Box;">.strokeColor</span> = strokeColor
    }
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li></ul>

我们在这个方法里实例化了一个PaintingBrushSettingsView,并添加到toolbar上,增加相应的约束,以及一些初始化设置和两个Block回调的处理。
然后修改viewDidLoad方法增加以下行为:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">//---</span>
<span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.toolbarEditingItems</span> = [
    <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">UIBarButtonItem</span>(barButtonSystemItem:<span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">.FlexibleSpace</span>,target: <span class="hljs-literal" style="color: rgb(0,102); Box-sizing: border-Box;">nil</span>,action: <span class="hljs-literal" style="color: rgb(0,102); Box-sizing: border-Box;">nil</span>),102); Box-sizing: border-Box;">UIBarButtonItem</span>(title: <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"完成"</span>,style:<span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">.Plain</span>,target: <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">self</span>,0); Box-sizing: border-Box;">"endSetting"</span>)
]
<span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.toolbarItems</span> = <span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.toolbar</span><span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">.items</span>

<span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.setupBrushSettingsView</span>()
<span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">//---</span></code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li></ul>

paintingBrushSettings方法里响应点击:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">@IBAction func paintingBrushSettings() {
    self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.currentSettingsView</span> = self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.viewWithTag</span>(<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">1</span>)
    self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.currentSettingsView</span>?<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.hidden</span> = false

    self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.updateToolbarForSettingsView</span>()
}


func updateToolbarForSettingsView() {
    self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.toolbarConstraintHeight</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.constant</span> = self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.currentSettingsView</span>!<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.height</span> + <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">44</span>

    self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.setItems</span>(self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.toolbarEditingItems</span>,animated: true)
    UIView<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.beginAnimations</span>(nil,context: nil)
    self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.layoutIfNeeded</span>()
    UIView<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.commitAnimations</span>()

    self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.bringSubviewToFront</span>(self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.currentSettingsView</span>!)
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li></ul>

updateToolbarForSettingsView也是一个工具方法,用于更新toolbar的高度。
由于我们采用了Auto Layout进行布局,动画要通过调用layoutIfNeeded方法来实现。
响应点击“完成”按钮的endSetting方法

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">@IBAction func endSetting() {
    <span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.toolbarConstraintHeight</span><span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">.constant</span> = <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">44</span>

    <span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.setItems</span>(<span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.toolbarItems</span>,animated: <span class="hljs-literal" style="color: rgb(0,102); Box-sizing: border-Box;">true</span>)

    <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">UIView</span><span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">.beginAnimations</span>(<span class="hljs-literal" style="color: rgb(0,context: <span class="hljs-literal" style="color: rgb(0,102); Box-sizing: border-Box;">nil</span>)
    <span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.layoutIfNeeded</span>()
    <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.commitAnimations</span>()

    <span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.currentSettingsView</span>?<span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">.hidden</span> = <span class="hljs-literal" style="color: rgb(0,102); Box-sizing: border-Box;">true</span>
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li></ul>

这么一来画笔设置就做完了,代码应该还是比较好理解,编译、运行后,应该能看到:

完成度已经很高了^^!


背景设置

整体的框架基本上已经在之前的工作中搭好了,我们快速过掉这一节。
Main.storyboard增加了一个title为“背景设置”的UIBarButtonItem,并将action连接到backgroundSettings方法上,你可以选择在插入“背景设置”之前,先插入一个FlexibleSpaceUIBarButtonItem
创建BackgroundSettingsVC类,继承自UIViewController,这与画笔设置继承于UIView不同,我们希望背景设置可以在用户的相册中选择照片,而使用UIImagePickerController的前提是要实现UIImagePickerControllerDelegateUINavigationControllerDelegate两个接口,如果让UIView来实现这两个接口会很奇怪。
创建一个BackgroundSettingsVC.xib文件

  1. 放置一个title为“从相册中选择背景图”的UIButton,约束为:左、上边距为8,宽度固定为135,高度固定为30。
  2. 放置一个RGBColorPicker,约束为:左、右边距为8,与UIButton的垂直间距为20,底部与superview齐平。
  3. 把UIButton的Touch Up Inside事件连接到BackgroundSettingsVCpickImage方法上;RGBColorPicker连接到colorPicker属性上。

看上去像这样:

BackgroundSettingsVC类的完整代码

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">class BackgroundSettingsVC : <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">UIViewController</span>,UIImagePickerControllerDelegate,UINavigationControllerDelegate {

    var backgroundImageChangedBlock: ((backgroundImage: <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">UIImage</span>) -> Void)?
    var backgroundColorChangedBlock: ((backgroundColor: <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">UIColor</span>) -> Void)?

    @IBOutlet private var colorPicker: RGBColorPicker!

    lazy private var pickerController: UIImagePickerController = {
        [unowned <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">self</span>] in
        let pickerController = UIImagePickerController()
        pickerController<span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">.delegate</span> = <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">self</span>

        <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> pickerController
    }()

    override func awakeFromNib() {
        <span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.awakeFromNib</span>()

        <span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.colorPicker</span><span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">.colorChangedBlock</span> = {
            [unowned <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">self</span>] (color: <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">UIColor</span>) in
            <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> let backgroundColorChangedBlock = <span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.backgroundColorChangedBlock</span> {
                backgroundColorChangedBlock(backgroundColor: color)
            }
        }
    }

    func setBackgroundColor(color: <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">UIColor</span>) {
        <span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.setCurrentColor</span>(color)
    }

    @IBAction func pickImage() {
        <span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.presentViewController</span>(<span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.pickerController</span>,102); Box-sizing: border-Box;">true</span>,completion: <span class="hljs-literal" style="color: rgb(0,102); Box-sizing: border-Box;">nil</span>)
    }

    <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// MARK: UIImagePickerControllerDelegate Methods</span>
    func imagePickerController(picker: UIImagePickerController,didFinishPickingMediaWithInfo info: [<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">NSObject</span> : AnyObject]) {
        let image = info[UIImagePickerControllerOriginalImage] as <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">UIImage</span>

        <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> let backgroundImageChangedBlock = <span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.backgroundImageChangedBlock</span> {
            backgroundImageChangedBlock(backgroundImage: image)
        }

        <span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.dismissViewControllerAnimated</span>(<span class="hljs-literal" style="color: rgb(0,0); Box-sizing: border-Box;">// MARK: UINavigationControllerDelegate Methods</span>
    func navigationController(navigationController: <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">UINavigationController</span>,willShowViewController viewController: <span class="hljs-built_in" style="color: rgb(102,animated: Bool) {
        <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">UIApplication</span><span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">.sharedApplication</span>()<span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">.setStatusBarHidden</span>(<span class="hljs-literal" style="color: rgb(0,withAnimation: <span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">.None</span>)
    }
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li><li style="Box-sizing: border-Box; padding: 0px 5px;">21</li><li style="Box-sizing: border-Box; padding: 0px 5px;">22</li><li style="Box-sizing: border-Box; padding: 0px 5px;">23</li><li style="Box-sizing: border-Box; padding: 0px 5px;">24</li><li style="Box-sizing: border-Box; padding: 0px 5px;">25</li><li style="Box-sizing: border-Box; padding: 0px 5px;">26</li><li style="Box-sizing: border-Box; padding: 0px 5px;">27</li><li style="Box-sizing: border-Box; padding: 0px 5px;">28</li><li style="Box-sizing: border-Box; padding: 0px 5px;">29</li><li style="Box-sizing: border-Box; padding: 0px 5px;">30</li><li style="Box-sizing: border-Box; padding: 0px 5px;">31</li><li style="Box-sizing: border-Box; padding: 0px 5px;">32</li><li style="Box-sizing: border-Box; padding: 0px 5px;">33</li><li style="Box-sizing: border-Box; padding: 0px 5px;">34</li><li style="Box-sizing: border-Box; padding: 0px 5px;">35</li><li style="Box-sizing: border-Box; padding: 0px 5px;">36</li><li style="Box-sizing: border-Box; padding: 0px 5px;">37</li><li style="Box-sizing: border-Box; padding: 0px 5px;">38</li><li style="Box-sizing: border-Box; padding: 0px 5px;">39</li><li style="Box-sizing: border-Box; padding: 0px 5px;">40</li><li style="Box-sizing: border-Box; padding: 0px 5px;">41</li><li style="Box-sizing: border-Box; padding: 0px 5px;">42</li><li style="Box-sizing: border-Box; padding: 0px 5px;">43</li><li style="Box-sizing: border-Box; padding: 0px 5px;">44</li><li style="Box-sizing: border-Box; padding: 0px 5px;">45</li><li style="Box-sizing: border-Box; padding: 0px 5px;">46</li><li style="Box-sizing: border-Box; padding: 0px 5px;">47</li><li style="Box-sizing: border-Box; padding: 0px 5px;">48</li><li style="Box-sizing: border-Box; padding: 0px 5px;">49</li><li style="Box-sizing: border-Box; padding: 0px 5px;">50</li></ul>

同样用两个Block进行回调;setBackgroundColor公共方法用于设置内部的RGBColorPicker的初始颜色状态;在UINavigationControllerDelegate里隐藏系统默认显示的状态栏。
回到ViewController,我们对背景设置进行测试。
setupBrushSettingsView方法一样,我们增加一个setupBackgroundSettingsView方法

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">func setupBackgroundSettingsView() {
    let backgroundSettingsVC = UINib(nibName: <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"BackgroundSettingsVC"</span>,68); Box-sizing: border-Box;">.first</span> as BackgroundSettingsVC

    self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.addConstraintsToToolbarForSettingsView</span>(backgroundSettingsVC<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.view</span>)

    backgroundSettingsVC<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.view</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.hidden</span> = true
    backgroundSettingsVC<span class="hljs-preprocessor" style="color: rgb(68,102); Box-sizing: border-Box;">2</span>
    backgroundSettingsVC<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.backgroundColor</span>!)

    self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.addChildViewController</span>(backgroundSettingsVC)

    backgroundSettingsVC<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.backgroundImageChangedBlock</span> = {
        [unowned self] (backgroundImage: UIImage) <span class="hljs-keyword" style="color: rgb(0,68); Box-sizing: border-Box;">.backgroundColor</span> = UIColor(patternImage: backgroundImage)
    }

    backgroundSettingsVC<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.backgroundColorChangedBlock</span> = {
        [unowned self] (backgroundColor: UIColor) <span class="hljs-keyword" style="color: rgb(0,68); Box-sizing: border-Box;">.backgroundColor</span> = backgroundColor
    }
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li><li style="Box-sizing: border-Box; padding: 0px 5px;">21</li></ul>

修改viewDidLoad方法

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',102); Box-sizing: border-Box;">.setupBrushSettingsView</span>()
<span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.setupBackgroundSettingsView</span>() <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// Added~!!!</span></code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li></ul>

实现backgroundSettings方法

<code class="language-swift hljs ruby has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; Box-sizing: border-Box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">@IBAction</span> func backgroundSettings() {
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">self</span>.currentSettingsView = <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">self</span>.toolbar.viewWithTag(<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">2</span>)
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">self</span>.currentSettingsView?.hidden = <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">false</span>

    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">self</span>.updateToolbarForSettingsView()
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li></ul>

编译、运行,现在你可以用不同的背景色(或背景图)了!


全屏绘图

到目前为止,Board一直显示不全(事实上,我很早就实现了全屏绘图,但是优先级一直被我排在最后),现在是时候来解决它了。
解决思路是这样的:当用户开始绘图的时候,我们把顶部和底部两个View隐藏;当用户结束绘图的时候,再让两个View显示
为了获取用户的绘图状态,我们需要在Board里加个“钩子”:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',0); Box-sizing: border-Box;">// 增加一个Block回调</span>
<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">var</span> drawingStateChangedBlock: ((state: DrawingState) <span class="hljs-subst" style="color: rgb(0,0); Box-sizing: border-Box;">-> </span>())<span class="hljs-subst" style="color: rgb(0,0); Box-sizing: border-Box;">?</span>

<span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">.</span>brush {
        <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// hook</span>
        <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">let</span> drawingStateChangedBlock <span class="hljs-subst" style="color: rgb(0,102); Box-sizing: border-Box;">.</span>drawingStateChangedBlock {
            drawingStateChangedBlock(state: <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>drawingState)
        }
        UIGraphicsBeginImageContext(<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">.</span>size)
        <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// ...</span></code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li></ul>

这样一来用户绘图的状态就在ViewController掌握中了。
ViewController想要控制两个View的话,还需要增加几个属性

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-annotation" style="color: rgb(155,136); Box-sizing: border-Box;">var</span> topView: UIView!
<span class="hljs-annotation" style="color: rgb(155,136); Box-sizing: border-Box;">var</span> topViewConstraintY: NSLayoutConstraint!
<span class="hljs-annotation" style="color: rgb(155,136); Box-sizing: border-Box;">var</span> toolbarConstraintBottom: NSLayoutConstraint!</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li></ul>

然后在viewDidLoad方法增加对“钩子”的处理:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.drawingStateChangedBlock</span> = {(state: DrawingState) -> () <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">in</span>
    if state != <span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.Moved</span> {
        UIView<span class="hljs-preprocessor" style="color: rgb(68,context: nil)
        if state == <span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.Began</span> {
            self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.topViewConstraintY</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.constant</span> = -self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.topView</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.height</span>
            self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.toolbarConstraintBottom</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.height</span>

            self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.layoutIfNeeded</span>()
            self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.layoutIfNeeded</span>()
        } else if state == <span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.Ended</span> {
            UIView<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.setAnimationDelay</span>(<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">1.0</span>)
            self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.constant</span> = <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">0</span>
            self<span class="hljs-preprocessor" style="color: rgb(68,102); Box-sizing: border-Box;">0</span>

            self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.layoutIfNeeded</span>()
        }
        UIView<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.commitAnimations</span>()
    }
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li></ul>

只有当状态为开始或结束的时候我们才需要更新UI状态,而且我们在结束的事件里延迟了1秒钟,这样用户可以暂时预览下全图。

依靠Auto Layout布局系统以及我们在钩子里对高度的处理,用户在设置页面绘图时也能完美运行。


保存到图库

最后一个功能:保存到图库!
在toolbar上插入一个title为“保存到图库”的UIBarButtonItem,还是可以先插入一个UIBarButtonItem,然后把action连接到ViewController的saveToAlbumy方法上:

<code class="language-swift hljs css has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; Box-sizing: border-Box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-at_rule" style="Box-sizing: border-Box;">@<span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">IBAction</span> func <span class="hljs-function" style="Box-sizing: border-Box; color: rgb(0,102);">saveToAlbum()</span> </span>{
    <span class="hljs-tag" style="color: rgb(0,0); Box-sizing: border-Box;">UIImageWriteToSavedPhotosAlbum</span>(<span class="hljs-tag" style="color: rgb(0,0); Box-sizing: border-Box;">self</span><span class="hljs-class" style="Box-sizing: border-Box; color: rgb(155,112,63);">.board</span><span class="hljs-class" style="Box-sizing: border-Box; color: rgb(155,63);">.takeImage</span>(),<span class="hljs-tag" style="color: rgb(0,0); Box-sizing: border-Box;">self</span>,"<span class="hljs-tag" style="color: rgb(0,0); Box-sizing: border-Box;">image</span><span class="hljs-pseudo" style="color: rgb(0,0); Box-sizing: border-Box;">:didFinishSavingWithError</span><span class="hljs-pseudo" style="color: rgb(0,0); Box-sizing: border-Box;">:contextInfo</span><span class="hljs-pseudo" style="color: rgb(0,0); Box-sizing: border-Box;">:"</span>,0); Box-sizing: border-Box;">nil</span>)
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li></ul>

我为Board添加一个新的公共方法:takeImage:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">func takeImage() -> UIImage {
    UIGraphicsBeginImageContext(self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.size</span>)

    self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.backgroundColor</span>?<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.setFill</span>()
    UIRectFill(self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.bounds</span>)

    self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.image</span>?<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.drawInRect</span>(self<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.bounds</span>)

    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return image
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li></ul>

然后是一个方法指针的回调:

<code class="language-swift hljs vbscript has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; Box-sizing: border-Box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">func image(image: UIImage,didFinishSavingWithError <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">error</span>: NSError?,contextInfo:UnsafePointer<Void>) {
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">let</span> <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">err</span> = <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">error</span> {
        UIAlertView(title: <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"错误"</span>,message: <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">err</span>.localizedDescription,delegate: nil,cancelButtonTitle: <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"确定"</span>).show()
    } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">else</span> {
        UIAlertView(title: <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"提示"</span>,message: <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"保存成功"</span>,0); Box-sizing: border-Box;">"确定"</span>).show()
    }
}</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; background-color: rgb(238,221); list-style: none; text-align: right;"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li></ul>


旅行到终点了~!


感谢一路的陪伴!

看了下,有些小长,文本+代码有2w3+,全部代码去除空行和空格有1w4+,直接贴代码会简单很多,但我始终觉得让代码完成功能并不是全部目的,代码背后隐藏的问题定义、设计、构建更有意义,毕竟软件开发完成“后”比完成“前”所花费的时间永远更多(除非是一个只有10行代码或者“一次性”的程序)。
希望与大家多多交流。

最后吐槽下CSDN新的Markdown编辑器,代码样式丑且不能自定义,而且有些代码高亮都无法识别。不过感觉草稿箱比以前更方便,问题主要还是集中在样式上,希望以后能不断改进,会一如既往的支持


更新——撤消与重做功能

Swift 绘图板功能完善以及终极优化


GitHub地址

DrawingBoard

相关文章

Swift 正式开源!Swift 团队很高兴宣布 Swift 开始开源新篇章。自从苹果发布 Swfit 编程语言,就成为了...
快,快,快!动动您的小手,分享给更多朋友! 苹果去年推出了全新的编程语言Swift,试图让iOS开发更简单...
开发者(KaiFaX) 面向开发者、程序员的专业平台! 和今年年初承诺的一样,苹果贴出了Swift语言的源码,...
本文由@Chun发表于Chun Tips :http://chun.tips/blog/2014/12/11/shi-yong-swift-gou-jian-zi-ding-yi...
本文由CocoaChina译者leon(社区ID)翻译 原文:THE RIGHT WAY TO WRITE A SINGLETON 在之前的帖子里聊过...
本文由CocoaChina译者leon(社区ID)翻译 原文:THE RIGHT WAY TO WRITE A SINGLETON 在之前的帖子里聊过...