Swift 全功能的绘图板开发

本文详细介绍了使用Swift开发全功能绘图板的过程,包括工程搭建、Board和BaseBrush类的设计、各种绘图工具(铅笔、直尺、虚线、矩形、圆形、橡皮擦)的实现,以及画笔设置、背景设置、全屏绘图和保存到图库等功能。文章通过策略设计模式和模板方法实现了绘图工具的灵活选择和交互,并提供了测试和设计思路。
摘要由CSDN通过智能技术生成

转载请注明出处:http://blog.csdn.net/zhangao0086/article/details/43836789

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

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

我们先做一些基础性的工作,比如创建工程。
这里写图片描述


工程搭建

先创建一个Single View Application 工程:
这里写图片描述
语言选择Swift
这里写图片描述
为了最大程度的利用屏幕区域,我们完全隐藏掉状态栏,在Info.plist里修改或添加这两个参数:
这里写图片描述
然后进入到Main.storyboard,开始搭建我们的UI。
我们给已存在的ViewControllerView添加一个UIImageView的子视图,背景色设为Light Gray,然后添加4个约束,由于要做一个全屏的画板,必须要让Constraint to margins保持没有选中的状态,否则左右两边会留下苹果建议的空白区域,最后把User Interaction Enabled打开:
这里写图片描述
这里写图片描述
然后我们回到ViewControllerView上:

  • 添加一个放工具栏的容器: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的实例了。
增加两个属性以及初始化方法:

var strokeWidth: CGFloat
var strokeColor: UIColor

override init() {
    self.strokeColor = UIColor.blackColor()
    self.strokeWidth = 1

    super.init()
}

required init(coder aDecoder: NSCoder) {
    self.strokeColor = UIColor.blackColor()
    self.strokeWidth = 1

    super.init(coder: aDecoder)
}

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

enum DrawingState {
    case Began, Moved, Ended
}

class Board: UIImageView {

    private var drawingState: DrawingState!

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

    // MARK: - touches methods

    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        self.drawingState = .Began
        self.drawingImage()
    }

    override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
        self.drawingState = .Moved
        self.drawingImage()
    }

    override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
        self.drawingState = .Ended
        self.drawingImage()
    }

    // MARK: - drawing

    private func drawingImage() {
        // 暂时为空实现
    }
}

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

BaseBrush

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

import CoreGraphics

protocol PaintBrush {

    func supportedContinuousDrawing() -> Bool;

    func drawInContext(context: CGContextRef)
}

class BaseBrush : NSObject, PaintBrush {
    var beginPoint: CGPoint!
    var endPoint: CGPoint!
    var lastPoint: CGPoint?

    var strokeWidth: CGFloat!

    func supportedContinuousDrawing() -> Bool {
        return false
    }

    func drawInContext(context: CGContextRef) {
        assert(false, "must implements in subclass.")
    }
}

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添加两个新的属性:

var brush: BaseBrush?

private var realImage: UIImage?    

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

// MARK: - touches methods

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
    if let brush = self.brush {
        brush.lastPoint = nil

        brush.beginPoint = touches.anyObject()!.locationInView(self)
        brush.endPoint = brush.beginPoint

        self.drawingState = .Began
        self.drawingImage()
    }
}

override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
    if let brush = self.brush {
        brush.endPoint = touches.anyObject()!.locationInView(self)

        self.drawingState = .Moved
        self.drawingImage()
    }
}

override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {
    if let brush = self.brush {
        brush.endPoint = nil
    }
}

override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
    if let brush = self.brush {
        brush.endPoint = touches.anyObject()!.locationInView(self)

        self.drawingState = .Ended

        self.drawingImage()
    }
}

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

private func drawingImage() {
    if let brush = self.brush {

        // 1.
        UIGraphicsBeginImageContext(self.bounds.size)

        // 2.
        let context = UIGraphicsGetCurrentContext()

        UIColor.clearColor().setFill()
        UIRectFill(self.bounds)

        CGContextSetLineCap(context, kCGLineCapRound)
        CGContextSetLineWidth(context, self.strokeWidth)
        CGContextSetStrokeColorWithColor(context, self.strokeColor.CGColor)

        // 3.
        if let realImage = self.realImage {
            realImage.drawInRect(self.bounds)
        }

        // 4.
        brush.strokeWidth = self.strokeWidth
        brush.drawInContext(context);
        CGContextStrokePath(context)

        // 5.
        let previewImage = UIGraphicsGetImageFromCurrentImageContext()
        if self.drawingState == .Ended || brush.supportedContinuousDrawing() {
            self.realImage = previewImage
        }

        UIGraphicsEndImageContext()

        // 6.
        self.image = previewImage;

        brush
  • 12
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 25
    评论
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值