首页 > 学院 > 开发设计 > 正文

Swift3.0学习实践-一个简单的画板(七色轨迹、可撤销、可清除、带橡皮擦)

2019-11-09 15:51:00
字体:
来源:转载
供稿:网友

写着玩儿的小程序,继续学习swift.运行效果+代码+知识点总结

运行效果:

           

代码:

Canvas类:画布,画图板状态管理、交互、处理手势
class Canvas:UIView{    //负责线条的生成、操作与管理    let pathCreator:PathCreator    //是否处于擦除状态    var isInErasering:Bool    //橡皮擦视图    let eraserView:UIView        override init(frame: CGRect) {        isInErasering = false        pathCreator = PathCreator()                eraserView = UIView.init()        eraserView.frame = CGRect(x: 0, y: 0, width: 10, height: 10)        eraserView.backgroundColor = UIColor.white        eraserView.alpha = 0        super.init(frame: frame)                self.backgroundColor = UIColor.black                self.addSubview(eraserView)                let revokeBut = UIButton(type: UIButtonType.system)        revokeBut.frame = CGRect(x: 20, y: 20, width: 80, height: 30)        revokeBut.setTitle("撤销", for: UIControlState.normal)        revokeBut.addTarget(self, action: #selector(revokeButClick), for: UIControlEvents.touchUpInside)        self.addSubview(revokeBut)                let cleanBut = UIButton(type: UIButtonType.system)        cleanBut.frame = CGRect(x: 110, y: 20, width: 80, height: 30)        cleanBut.setTitle("清空", for: UIControlState.normal)        cleanBut.addTarget(self, action: #selector(cleanButClick), for: UIControlEvents.touchUpInside)        self.addSubview(cleanBut)            let eraserBut = UIButton(type: UIButtonType.system)        eraserBut.frame = CGRect(x: 200, y: 20, width:80, height: 30)        eraserBut.setTitle("橡皮", for: UIControlState.normal)        eraserBut.setTitle("画笔", for: UIControlState.selected)        eraserBut.addTarget(self, action: #selector(eraserButClick(but:)), for: UIControlEvents.touchUpInside)        self.addSubview(eraserBut)                let ges = UipanGestureRecognizer(target: self, action:#selector(handleGes(ges:)))        ges.maximumNumberOfTouches = 1        self.addGestureRecognizer(ges)    }        required public init?(coder aDecoder: NSCoder) {        fatalError("init(coder:) has not been implemented")    }        override public func layoutSubviews() {            }        @objc PRivate func handleGes(ges:UIPanGestureRecognizer) -> Void {        let point = ges.location(in: self)        switch ges.state {        case UIGestureRecognizerState.began:            if isInErasering {                //擦除状态,显示出橡皮擦                eraserView.alpha = 1                eraserView.center = point            }            //生成新的一笔            pathCreator.addNewPath(to: point,isEraser: isInErasering)            self.setNeedsDisplay()        case UIGestureRecognizerState.changed:            if isInErasering {                //移动橡皮擦                eraserView.center = ges.location(in: self)            }            //更新当前笔画路径            pathCreator.addLineForCurrentPath(to: point,isEraser:isInErasering)            self.setNeedsDisplay()        case UIGestureRecognizerState.ended:            if isInErasering {                //擦除状态,隐藏橡皮擦                eraserView.alpha = 0                eraserView.center = ges.location(in: self)            }            //更新当前笔画路径            pathCreator.addLineForCurrentPath(to: point,isEraser: isInErasering)            self.setNeedsDisplay()        case UIGestureRecognizerState.cancelled:            print("cancel")        case UIGestureRecognizerState.failed:            print("fail")        default:            return        }    }        override public func draw(_ rect: CGRect) {        //画线        pathCreator.drawPaths()    }        @objc private func revokeButClick()->Void{        //撤销操作        pathCreator.revoke()        self.setNeedsDisplay()    }        @objc private func cleanButClick()->Void{        //清空操作        pathCreator.clean()        self.setNeedsDisplay()    }        @objc private func eraserButClick(but:UIButton)->Void{        //切换画图与擦除状态        if but.isSelected {            but.isSelected = false            isInErasering = false        }else{            but.isSelected = true            isInErasering = true        }    }}

PathCreator:具体线条绘制、管理

//每条子线段信息struct BezierInfo{    let path:UIBezierPath//具体线段    let color:UIColor//线段对应颜色    init(path:UIBezierPath,color:UIColor){        self.path = path        self.color = color    }}class PathCreator{    //所有笔画    private var paths:[NSMutableArray]?    //笔画内当前子线段    private var currentBezierPathInfo:BezierInfo?    //当前笔画的所有子线段    private var currentPath:NSMutableArray?    //当前笔画已经采集处理了几个触摸点    private var pointCountInOnePath = 0        static let colors = [UIColor.red,UIColor.orange,UIColor.yellow,UIColor.green,UIColor.blue,UIColor.gray,UIColor.purple]    init() {        paths = []    }    //添加新笔画    func addNewPath(to:CGPoint,isEraser:Bool)->Void{        //创建起始线段        let path = UIBezierPath()        path.lineWidth = 5        path.move(to: to)        path.lineJoinStyle = CGLineJoin.round        path.lineCapStyle = CGLineCap.round        if !isEraser {            //绑定线段与颜色信息            currentBezierPathInfo = BezierInfo(path: path, color: PathCreator.colors[0])        }else{            //处于擦除模式,颜色与画板背景色相同            currentBezierPathInfo = BezierInfo(path: path, color: UIColor.black)        }        //新建一个笔画        currentPath = NSMutableArray.init()        //将起始线段加入当前笔画        currentPath!.add(currentBezierPathInfo)        pointCountInOnePath = 0        //将当前笔画加入笔画数组        paths!.append(currentPath!)    }    //添加新的点,更新当前笔画路径    func addLineForCurrentPath(to:CGPoint,isEraser:Bool) -> Void {        pointCountInOnePath += 1//同一笔画内,每7个点换一次颜色        if pointCountInOnePath % 7 == 0{//换颜色            if let currentBezierPathInfo = currentBezierPathInfo{                //将当前点加入当前子线段,更新当前子线段路径                currentBezierPathInfo.path.addLine(to: to)            }            //生成新的子线段            let path = UIBezierPath()            path.lineWidth = 5            path.move(to: to)            path.lineJoinStyle = CGLineJoin.round            path.lineCapStyle = CGLineCap.round            if !isEraser{                //给当前子线段设置下一个颜色                currentBezierPathInfo = BezierInfo(path: path, color: PathCreator.colors[currentPath!.count % 7])            }else{                //处于擦除模式,颜色与画板背景色相同                currentBezierPathInfo = BezierInfo(path: path, color: UIColor.black)            }            //将当前子线段加入当前笔画            currentPath!.add(currentBezierPathInfo)        }else{            if let currentBezierPathInfo = currentBezierPathInfo{                //将当前点加入当前子线段,更新当前子线段路径                currentBezierPathInfo.path.addLine(to: to)            }        }    }        func drawPaths()->Void{        //画线        let pathCount = paths!.count        for i in 0..<pathCount{            //取出所有笔画            let onePath = paths![i]            let onePathCount = onePath.count            for j in 0..<onePathCount{                //绘制每条笔画内每个子线段                let pathInfo = onePath.object(at: j) as! BezierInfo                pathInfo.color.set()                pathInfo.path.stroke()            }        }    }        func revoke()->Void{        //移走上一笔画        if paths!.count > 0 {            paths!.removeLast()        }    }        func clean()->Void{        //移走所有笔画        paths!.removeAll()    }}

知识点总结:

1.结构体是值传递

一个基础概念,但开始使用时还是给忘了。数组[]在swift中是结构体(struct)实现,值传递。最开始把currentPath声明为了[],添加到paths[]中后,后续再去往currentPath中添加元素,paths中的对应的currentpath对象内容并未随之发生改变,后将currentPath改为了NSMutableArray(引用传递).

2.selector、@objc、private

(纯)swift与oc采用了不同的运行机制,swift不再采用与oc一样的运行时(runtime)与消息分发机制,selector作为oc运行机制的产物,swift中也对其进行了保留与支持。

@objc修饰符的作用是将swift定义的类、方法等暴露给oc。

于是,下列selector中指定的方法,都要使用@objc进行修饰

cleanBut.addTarget(self, action: #selector(cleanButClick), for: UIControlEvents.touchUpInside)
let ges = UIPanGestureRecognizer(target: self, action:#selector(handleGes(ges:)))如果一个swift类继承自NSObject,swift会默认给该类的非private属性或方法加上@objc修饰。因为Canvas类(->UIView->UIResponder->NSObject)继承自NSObject,所以其属性或方法(非private)都会被自动加上@objc修饰但是因为我代码中的这几个selector指向的方法都声明为了private,所以还是需要手动去做@objc修饰(如果是非private的,可以不写@objc)
@objc private func handleGes(ges:UIPanGestureRecognizer) -> Void

3.required的构造函数

required用于修饰构造方法,用于要求子类必需实现对应的构造方法如果子类中没有实现任何构造方法,则不必去显式的实现父类要求的required构造方法;而当子类中有定义实现构造方法时,则必需显式的去实现父类要求的required构造方法,同时还要保留required修饰.当实现一个类Canvas继承自UIView时,我们可以看到编译器强制要求我们实现构造方法
public init?(coder aDecoder: NSCoder)通过xcode找到该方法是在NSCoding协议中被定义的
public protocol NSCoding {    public func encode(with aCoder: NSCoder)    public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER}可以看到,此处并没有进行requird修饰,为什么还要求强制实现该构造方法呢?因为在协议中规定的构造方法,不用显式进行requird修饰,实现协议的对应类默认必需要去实现协议中规定的构造方法,且加上requird修饰

4.as

let x:UInt16 = 100let y:UInt8 = 10//x + y会报错,不自动类型转换,更安全let n = UInt8(x) + y上面例子中,当我们进行值类型之间的类型转换(UInt16->UInt8)时,其实借助的是UInt8的构造方法
/// Create an instance initialized to `value`.    public init(integerLiteral value: UInt8)而当引用类型之间需要进行强制转换时,则需要借助as操作符因为转换可能失败(两个不相关的类之间进行转换),所以需要使用as?,转换结果为一个可选型,不成功时,可选型值为nil当然,如果可以肯定转换是成功的,则可以使用as!进行转换,结果为目标类型的对象。另外,看下面这个例子
var people:People?let man:Man = Man()people = manprint(people)//可选型变量let beMan = people as! Manprint (beMan)//强制转化后beMan不是可选型
var people:People?let man:Man = Man()people = manprint(people)//可选型变量let beMan = people as! Man?print (beMan)//强制转化后beMan为可选型转换后的结果类型完全由as!后面的目标类型决定,即便原对象在转换之前是可选型对象,但如果转换的目标类型不是可选型,则转换后得到的也就不是一个可选型了
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表