Swift3.0学习实践-一个简单的画板(七色轨迹可撤销可清除带橡皮擦)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift3.0学习实践-一个简单的画板(七色轨迹可撤销可清除带橡皮擦)相关的知识,希望对你有一定的参考价值。
写着玩儿的小程序,继续学习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)
- public protocol NSCoding {
- public func encode(with aCoder: NSCoder)
- public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER
- }
因为在协议中规定的构造方法,不用显式进行requird修饰,实现协议的对应类默认必需要去实现协议中规定的构造方法,且加上requird修饰
4.as
- let x:UInt16 = 100
- let y:UInt8 = 10
- //x + y会报错,不自动类型转换,更安全
- let n = UInt8(x) + y
- /// Create an instance initialized to `value`.
- public init(integerLiteral value: UInt8)
因为转换可能失败(两个不相关的类之间进行转换),所以需要使用as?,转换结果为一个可选型,不成功时,可选型值为nil
当然,如果可以肯定转换是成功的,则可以使用as!进行转换,结果为目标类型的对象。
另外,看下面这个例子
- var people:People?
- let man:Man = Man()
- people = man
- print(people)//可选型变量
- let beMan = people as! Man
- print (beMan)//强制转化后beMan不是可选型
- var people:People?
- let man:Man = Man()
- people = man
- print(people)//可选型变量
- let beMan = people as! Man?
- print (beMan)//强制转化后beMan为可选型
转换后的结果类型完全由as!后面的目标类型决定,即便原对象在转换之前是可选型对象,但如果转换的目标类型不是可选型,则转换后得到的也就不是一个可选型了
以上是关于Swift3.0学习实践-一个简单的画板(七色轨迹可撤销可清除带橡皮擦)的主要内容,如果未能解决你的问题,请参考以下文章