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

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

运行效果:

           

代码:

Canvas类:画布,画图板状态管理、交互、处理手势

[plain] view plain copy

  1. class Canvas:UIView{
  2. //负责线条的生成、操作与管理
  3. let pathCreator:PathCreator
  4. //是否处于擦除状态
  5. var isInErasering:Bool
  6. //橡皮擦视图
  7. let eraserView:UIView
  8. override init(frame: CGRect) {
  9. isInErasering = false
  10. pathCreator = PathCreator()
  11. eraserView = UIView.init()
  12. eraserView.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
  13. eraserView.backgroundColor = UIColor.white
  14. eraserView.alpha = 0
  15. super.init(frame: frame)
  16. self.backgroundColor = UIColor.black
  17. self.addSubview(eraserView)
  18. let revokeBut = UIButton(type: UIButtonType.system)
  19. revokeBut.frame = CGRect(x: 20, y: 20, width: 80, height: 30)
  20. revokeBut.setTitle("撤销", for: UIControlState.normal)
  21. revokeBut.addTarget(self, action: #selector(revokeButClick), for: UIControlEvents.touchUpInside)
  22. self.addSubview(revokeBut)
  23. let cleanBut = UIButton(type: UIButtonType.system)
  24. cleanBut.frame = CGRect(x: 110, y: 20, width: 80, height: 30)
  25. cleanBut.setTitle("清空", for: UIControlState.normal)
  26. cleanBut.addTarget(self, action: #selector(cleanButClick), for: UIControlEvents.touchUpInside)
  27. self.addSubview(cleanBut)
  28. let eraserBut = UIButton(type: UIButtonType.system)
  29. eraserBut.frame = CGRect(x: 200, y: 20, width:80, height: 30)
  30. eraserBut.setTitle("橡皮", for: UIControlState.normal)
  31. eraserBut.setTitle("画笔", for: UIControlState.selected)
  32. eraserBut.addTarget(self, action: #selector(eraserButClick(but:)), for: UIControlEvents.touchUpInside)
  33. self.addSubview(eraserBut)
  34. let ges = UIPanGestureRecognizer(target: self, action:#selector(handleGes(ges:)))
  35. ges.maximumNumberOfTouches = 1
  36. self.addGestureRecognizer(ges)
  37. }
  38. required public init?(coder aDecoder: NSCoder) {
  39. fatalError("init(coder:) has not been implemented")
  40. }
  41. override public func layoutSubviews() {
  42. }
  43. @objc private func handleGes(ges:UIPanGestureRecognizer) -> Void {
  44. let point = ges.location(in: self)
  45. switch ges.state {
  46. case UIGestureRecognizerState.began:
  47. if isInErasering {
  48. //擦除状态,显示出橡皮擦
  49. eraserView.alpha = 1
  50. eraserView.center = point
  51. }
  52. //生成新的一笔
  53. pathCreator.addNewPath(to: point,isEraser: isInErasering)
  54. self.setNeedsDisplay()
  55. case UIGestureRecognizerState.changed:
  56. if isInErasering {
  57. //移动橡皮擦
  58. eraserView.center = ges.location(in: self)
  59. }
  60. //更新当前笔画路径
  61. pathCreator.addLineForCurrentPath(to: point,isEraser:isInErasering)
  62. self.setNeedsDisplay()
  63. case UIGestureRecognizerState.ended:
  64. if isInErasering {
  65. //擦除状态,隐藏橡皮擦
  66. eraserView.alpha = 0
  67. eraserView.center = ges.location(in: self)
  68. }
  69. //更新当前笔画路径
  70. pathCreator.addLineForCurrentPath(to: point,isEraser: isInErasering)
  71. self.setNeedsDisplay()
  72. case UIGestureRecognizerState.cancelled:
  73. print("cancel")
  74. case UIGestureRecognizerState.failed:
  75. print("fail")
  76. default:
  77. return
  78. }
  79. }
  80. override public func draw(_ rect: CGRect) {
  81. //画线
  82. pathCreator.drawPaths()
  83. }
  84. @objc private func revokeButClick()->Void{
  85. //撤销操作
  86. pathCreator.revoke()
  87. self.setNeedsDisplay()
  88. }
  89. @objc private func cleanButClick()->Void{
  90. //清空操作
  91. pathCreator.clean()
  92. self.setNeedsDisplay()
  93. }
  94. @objc private func eraserButClick(but:UIButton)->Void{
  95. //切换画图与擦除状态
  96. if but.isSelected {
  97. but.isSelected = false
  98. isInErasering = false
  99. }else{
  100. but.isSelected = true
  101. isInErasering = true
  102. }
  103. }
  104. }

PathCreator:具体线条绘制、管理

[plain] view plain copy

  1. //每条子线段信息
  2. struct BezierInfo{
  3. let path:UIBezierPath//具体线段
  4. let color:UIColor//线段对应颜色
  5. init(path:UIBezierPath,color:UIColor){
  6. self.path = path
  7. self.color = color
  8. }
  9. }
  10. class PathCreator{
  11. //所有笔画
  12. private var paths:[NSMutableArray]?
  13. //笔画内当前子线段
  14. private var currentBezierPathInfo:BezierInfo?
  15. //当前笔画的所有子线段
  16. private var currentPath:NSMutableArray?
  17. //当前笔画已经采集处理了几个触摸点
  18. private var pointCountInOnePath = 0
  19. static let colors = [UIColor.red,UIColor.orange,UIColor.yellow,UIColor.green,UIColor.blue,UIColor.gray,UIColor.purple]
  20. init() {
  21. paths = []
  22. }
  23. //添加新笔画
  24. func addNewPath(to:CGPoint,isEraser:Bool)->Void{
  25. //创建起始线段
  26. let path = UIBezierPath()
  27. path.lineWidth = 5
  28. path.move(to: to)
  29. path.lineJoinStyle = CGLineJoin.round
  30. path.lineCapStyle = CGLineCap.round
  31. if !isEraser {
  32. //绑定线段与颜色信息
  33. currentBezierPathInfo = BezierInfo(path: path, color: PathCreator.colors[0])
  34. }else{
  35. //处于擦除模式,颜色与画板背景色相同
  36. currentBezierPathInfo = BezierInfo(path: path, color: UIColor.black)
  37. }
  38. //新建一个笔画
  39. currentPath = NSMutableArray.init()
  40. //将起始线段加入当前笔画
  41. currentPath!.add(currentBezierPathInfo)
  42. pointCountInOnePath = 0
  43. //将当前笔画加入笔画数组
  44. paths!.append(currentPath!)
  45. }
  46. //添加新的点,更新当前笔画路径
  47. func addLineForCurrentPath(to:CGPoint,isEraser:Bool) -> Void {
  48. pointCountInOnePath += 1//同一笔画内,每7个点换一次颜色
  49. if pointCountInOnePath % 7 == 0{//换颜色
  50. if let currentBezierPathInfo = currentBezierPathInfo{
  51. //将当前点加入当前子线段,更新当前子线段路径
  52. currentBezierPathInfo.path.addLine(to: to)
  53. }
  54. //生成新的子线段
  55. let path = UIBezierPath()
  56. path.lineWidth = 5
  57. path.move(to: to)
  58. path.lineJoinStyle = CGLineJoin.round
  59. path.lineCapStyle = CGLineCap.round
  60. if !isEraser{
  61. //给当前子线段设置下一个颜色
  62. currentBezierPathInfo = BezierInfo(path: path, color: PathCreator.colors[currentPath!.count % 7])
  63. }else{
  64. //处于擦除模式,颜色与画板背景色相同
  65. currentBezierPathInfo = BezierInfo(path: path, color: UIColor.black)
  66. }
  67. //将当前子线段加入当前笔画
  68. currentPath!.add(currentBezierPathInfo)
  69. }else{
  70. if let currentBezierPathInfo = currentBezierPathInfo{
  71. //将当前点加入当前子线段,更新当前子线段路径
  72. currentBezierPathInfo.path.addLine(to: to)
  73. }
  74. }
  75. }
  76. func drawPaths()->Void{
  77. //画线
  78. let pathCount = paths!.count
  79. for i in 0..<pathCount{
  80. //取出所有笔画
  81. let onePath = paths![i]
  82. let onePathCount = onePath.count
  83. for j in 0..<onePathCount{
  84. //绘制每条笔画内每个子线段
  85. let pathInfo = onePath.object(at: j) as! BezierInfo
  86. pathInfo.color.set()
  87. pathInfo.path.stroke()
  88. }
  89. }
  90. }
  91. func revoke()->Void{
  92. //移走上一笔画
  93. if paths!.count > 0 {
  94. paths!.removeLast()
  95. }
  96. }
  97. func clean()->Void{
  98. //移走所有笔画
  99. paths!.removeAll()
  100. }
  101. }

知识点总结:

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进行修饰

[plain] view plain copy

  1. cleanBut.addTarget(self, action: #selector(cleanButClick), for: UIControlEvents.touchUpInside)

[plain] view plain copy

  1. 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)

[plain] view plain copy

  1. @objc private func handleGes(ges:UIPanGestureRecognizer) -> Void

3.required的构造函数

required用于修饰构造方法,用于要求子类必需实现对应的构造方法

如果子类中没有实现任何构造方法,则不必去显式的实现父类要求的required构造方法;而当子类中有定义实现构造方法时,则必需显式的去实现父类要求的required构造方法,同时还要保留required修饰.

当实现一个类Canvas继承自UIView时,我们可以看到编译器强制要求我们实现构造方法

[plain] view plain copy

  1. public init?(coder aDecoder: NSCoder)

通过xcode找到该方法是在NSCoding协议中被定义的

[plain] view plain copy

  1. public protocol NSCoding {
  2. public func encode(with aCoder: NSCoder)
  3. public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER
  4. }

可以看到,此处并没有进行requird修饰,为什么还要求强制实现该构造方法呢?

因为在协议中规定的构造方法,不用显式进行requird修饰,实现协议的对应类默认必需要去实现协议中规定的构造方法,且加上requird修饰

4.as

[plain] view plain copy

  1. let x:UInt16 = 100
  2. let y:UInt8 = 10
  3. //x + y会报错,不自动类型转换,更安全
  4. let n = UInt8(x) + y

上面例子中,当我们进行值类型之间的类型转换(UInt16->UInt8)时,其实借助的是UInt8的构造方法

[plain] view plain copy

  1. /// Create an instance initialized to `value`.
  2. public init(integerLiteral value: UInt8)

而当引用类型之间需要进行强制转换时,则需要借助as操作符

因为转换可能失败(两个不相关的类之间进行转换),所以需要使用as?,转换结果为一个可选型,不成功时,可选型值为nil

当然,如果可以肯定转换是成功的,则可以使用as!进行转换,结果为目标类型的对象。

另外,看下面这个例子

[plain] view plain copy

  1. var people:People?
  2. let man:Man = Man()
  3. people = man
  4. print(people)//可选型变量
  5. let beMan = people as! Man
  6. print (beMan)//强制转化后beMan不是可选型

[plain] view plain copy

  1. var people:People?
  2. let man:Man = Man()
  3. people = man
  4. print(people)//可选型变量
  5. let beMan = people as! Man?
  6. print (beMan)//强制转化后beMan为可选型

转换后的结果类型完全由as!后面的目标类型决定,即便原对象在转换之前是可选型对象,但如果转换的目标类型不是可选型,则转换后得到的也就不是一个可选型了

时间: 2024-12-28 21:28:37

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

javascript源码之js实现的一个简单的网页拾色器

今天学习了window对象,跟着学习了一个简单的网页拾色器的demo,拿出来和大家分享. 主页面代码: <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>拾色器</title></head><body><h1>网页拾色器</h1>    <scr

Android 多媒体开发学习之制作一个简单的画板

一个简单的画板,可以绘制,可以选择颜色,可以保存. 当然了这种工具一般常用的通讯软件都是会有的,比如QQ, 飞秋等 其中我们必须监听手指的触摸事件,手指的触摸事件就分为3种: 按下,抬起,移动. 通常我们只需要关系按下的时候,然后就是整个手指滑动的过程.然后将手指滑动的过程绘制为不同的直线.当然也可以设置绘制的颜色,绘制直线的宽度等. public class MainActivity extends Activity { private int TouchX; private int Touc

在iOS中实现一个简单的画板App

在这个随笔中,我们要为iPhone实现一个简单的画板App,类似于手写输入中写字的面板.但是我们的画板支持画笔颜色的选择. 首先需要指出的是,这个demo中使用QuarzCore进行绘画,而不是OpenGL.这两个都可以实现类似的功能,区别是OpenGL更快,但是QuarzCore更简单. 第一步,新建Xcode项目,项目名称就叫SimplePaint. 第二步,添加QuarzCore.framework到项目中. 第三步,创建一个新类,类名叫Line.它代表在iPhone的屏幕上绘画时候的线.

iOS 制作一个简单的画板

制作简单画板 作为iOS初学者,在学习完UI的几个简单控件(UILable,UITextField,UIButton)之后,就可以制作一个简单的画图板demo,以下是具体制作流程(在MRC下),如有不足之处,还请各位大神们指教 0.0. 1.搭建界面,主要由UIButton,UITextField组成,底部的按钮是UITextField的一个自定义键盘(inputView) . - (void)viewDidLoad { [super viewDidLoad]; //创建菜单按钮 UIButto

0915-----Linux设备驱动 学习笔记----------一个简单的字符设备驱动程序

0.前言 研究生生活一切都在步入正轨,我也开始了新的学习,因为实在不想搞存储,所以就决定跟师兄学习设备驱动,看了两星期书,终于有点头绪了,开始记录吧! 1.准备工作 a)查看内核版本 uname -r b)安装内核源码树(http://www.cnblogs.com/Jezze/archive/2011/12/23/2299871.html) 在www.linux.org上下载源码编译,这里是.xz格式,需要安装解压工具,xz-utils: 解压方法示例:xz -d linux-3.1-rc4.

Hibernate学习——建立一个简单的Hibernate项目

最近老师让做个web小应用,大三的时候学习过一点J2EE的东西,也做过一些web相关的XXX管理系统,都是用servlet,jsp这些完成的,虽然勉强能够完成任务,但其中各种代码掺杂在一起,不好看而且维护起来也很麻烦,出了一点问题要调试好久才能解决.这次打算让自己多学一点东西,在项目的架构上我使用了struts2实现了多层代码的分离,在数据库方面,因为以往的经历都需要对连接数据库进行一个封装,里面包含有数据库的连接,记录的添加,查询,修改和删除等操作,每次使用的过程中都需要先实现一个连接对象然后

WCF学习——构建一个简单的WCF应用(一)

本文的WCF服务应用功能很简单,却涵盖了一个完整WCF应用的基本结构.希望本文能对那些准备开始学习WCF的初学者提供一些帮助. 在这个例子中,我们将实现一个简单的计算器和传统的分布式通信框架一样,WCF本质上提供一个跨进程.跨机器.跨网络的服务调用.在本例中,客户端和WCF应用服务通过运行在同一台机器上的不同进程模拟. 步骤一.构建整个解决方案 1.创建一个空白的解决方案 2.添加四个项目和引用及关系  Service.Interface  用于定义服务契约的类库项目,引用WCF核心程序集Sys

【iOS开发-50】利用创建新的类实现代码封装,从而不知不觉实践一个简单的MVC实验

接上次案例谈代码封装.上次案例见:[iOS开发-48]九宫格布局案例:自动布局.字典转模型运用.id和instancetype区别.xib重复视图运用及与nib关系 代码封装的原则是:要保证视图控制器尽量少的接触到其他对象的属性,也就是说,尽量把数据或者属性封装到一个类里面,然后利用类或者对象的方法来调用或者设置数据.而是赤裸裸地把属性都写在视图控制器中.核心作用在于:减少视图控制器的代码量,把数据和属性的处理封装起来,这样也便于其他视图控制器的使用. 要做到的结果就是如下(我们要根据数组里面的

Openfire/XMPP学习之——一个简单的Smack样例

昨天讲了Openfire的搭建和配置,今天来讲一下Smack.如果对如何搭建和配置Openfire的,可以参考Openfire/XMPP学习之——Openfire的安装.配置. Smack是一个开源,易于使用的XMPP客户端类库.Smack API, 是一个 Java 的XMPP Client Library,也是由Jive Software开发. 优点:编程简单. 缺点:API并非为大量并发用户设计,每个客户要1个线程,占用资源大,1台机器只能模拟有限(数千个)客户.Smack是一个用 jav