在这个随笔中,我们要为iPhone实现一个简单的画板App。

首先需要指出的是,这个demo中使用QuarzCore进行绘画,而不是OpenGL。这两个都可以实现类似的功能,区别是OpenGL更快,但是QuarzCore更简单。
 
第一步,新建Xcode项目,项目名称就叫SimplePaint。
第二步,添加QuarzCore.framework到项目中。
 
 
第三步,创建一个新类,类名叫Line。它代表在iPhone的屏幕上绘画时候的线。因为不管是画一条直线还是一条曲线,都可以看做是多条短的直线连接起来的。那么Line需要的是什么属性呢?简单点就是,这条线的开始点和结束点,还有这条线的颜色。所以,打开刚刚创建的Line类,
修改Line.h:
 
然后修改Line.m:
 
第四步,接下来创建另一个类,类名叫做ColorPiker。它代表颜色选择器,我们可以点击多个颜色中的一个作为画笔的颜色进行绘画。以下是ColorPiker.h的代码:
 
aColorPikerIsSelected是一个委托,当当前选择器被选中后,它可以把当前选中的颜色选择器的颜色值传递出去,传递给实现了这个委托的类。在我们的这个demo中,我们会让画板View的实现此委托。
 
实现ColorPiker.m:
 
 
第五步,接下来我们就创建我们的画板View,它代表可以画图的那部分有效区域。创建一个新类叫做TouchDrawView。修改TouchDrawView.h的内容:
 
刚刚上文中也提过了,我们画图的主要思想就是把多个短线首尾连接起来,就可以成为一条轨迹。所以我们的画板有一个array,它的item就是Line类型,它就是滑过轨迹的所有Line的集合。我们还有一个单独的Line对象,表示在绘画过程中,当前正在画的这条线。另外有一个Color类型的属性,表示线的颜色,也就是用来保存ColorPiker传递出来的颜色值。
 
实现TouchDrawView.m
 //
 //  TouchDrawView.m
 //  CaplessCoderPaint
 //
 //  Created by backslash112 on 14/10/29.
 //  Copyright (c) 2014年 backslash112. All rights reserved.
 //

 #import "TouchDrawView.h"
 #import "Common.h"

 @implementation TouchDrawView
 {
 }
 @synthesize currentLine;
 @synthesize linesCompleted;
 @synthesize drawColor;

 - (id)initWithCoder:(NSCoder *)c
 {
     self = [super initWithCoder:c];
     if (self) {
         linesCompleted = [[NSMutableArray alloc] init];
         [self setMultipleTouchEnabled:YES];

         drawColor = [UIColor blackColor];
         [self becomeFirstResponder];
     }
     return self;
 }

 //  It is a method of UIView called every time the screen needs a redisplay or refresh.
 - (void)drawRect:(CGRect)rect
 {
     CGContextRef context = UIGraphicsGetCurrentContext();
     CGContextSetLineWidth(context, 5.0);
     CGContextSetLineCap(context, kCGLineCapRound);
     [drawColor set];
     for (Line *line in linesCompleted) {
         [[line color] set];
         CGContextMoveToPoint(context, [line begin].x, [line begin].y);
         CGContextAddLineToPoint(context, [line end].x, [line end].y);
         CGContextStrokePath(context);
     }
 }

 - (void)undo
 {
     if ([self.undoManager canUndo]) {
         [self.undoManager undo];
         [self setNeedsDisplay];
     }
 }

 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
 {
     [self.undoManager beginUndoGrouping];
     for (UITouch *t in touches) {
         // Create a line for the value
         CGPoint loc = [t locationInView:self];
         Line *newLine = [[Line alloc] init];
         [newLine setBegin:loc];
         [newLine setEnd:loc];
         [newLine setColor:drawColor];
         currentLine = newLine;
     }
 }

 - (void)addLine:(Line*)line
 {
     [[self.undoManager prepareWithInvocationTarget:self] removeLine:line];
     [linesCompleted addObject:line];
 }

 - (void)removeLine:(Line*)line
 {
     if ([linesCompleted containsObject:line])
         [linesCompleted removeObject:line];
 }

 - (void)removeLineByEndPoint:(CGPoint)point
 {
     NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
         Line *evaluatedLine = (Line*)evaluatedObject;
         return evaluatedLine.end.x == point.x &&
         evaluatedLine.end.y == point.y;
     }];
     NSArray *result = [linesCompleted filteredArrayUsingPredicate:predicate];
     ) {
         [linesCompleted removeObject:result[]];
     }
 }

 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
 {
     for (UITouch *t in touches) {
         [currentLine setColor:drawColor];
         CGPoint loc = [t locationInView:self];
         [currentLine setEnd:loc];

         if (currentLine) {
             [self addLine:currentLine];
         }
         Line *newLine = [[Line alloc] init];
         [newLine setBegin:loc];
         [newLine setEnd:loc];
         [newLine setColor:drawColor];
         currentLine = newLine;
     }
     [self setNeedsDisplay];
 }

 - (void)endTouches:(NSSet *)touches
 {
     [self setNeedsDisplay];
 }

 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
 {
     [self endTouches:touches];
     [self.undoManager endUndoGrouping];
 }

 - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
 {
     [self endTouches:touches];
 }

 - (BOOL)canBecomeFirstResponder
 {
     return YES;
 }

 - (void)didMoveToWindow
 {
     [self becomeFirstResponder];
 }

 - (id)initWithFrame:(CGRect)frame
 {
     self = [super initWithFrame:frame];
     if (self) {
         // Initialization code
     }
     return self;
 }

 @end

这个文件包含了主要的逻辑,说明下主要方法的作用:

-(id)initWithCoder:当此view被创建的时候这个方法自动调用,所以你不一定必须要实现它;当时当你想在初始化的时候做一些别的工作的时候你就需要实现它。

-(void)drawRect:每次当屏幕需要重新显示或者刷新的时候这个方法会被调用。

-(void)touchBegan:当你的手指点击到屏幕的时候这个方法会被调用。

-(void)touchMove:当你的手指点击屏幕后开始在屏幕移动,它会被调用。随着手指的移动,相关的对象会秩序发送该消息。

-(void)touchEnd:当你的手指点击屏幕之后离开的时候,它会被调用。

还需要讲解下的是,当每次touchMove方法中,往array中添加入了新的Line,要想在画布中显示出来,需要刷新下页面,调用[self setNeedsDisplay]方法即可。
 
基本的主要逻辑就是这样,我们还差什么?当然是UI!
 
第六步,创建一个 .xib文件,叫做TouchDrawViewController.xib。在右边的工具箱中添加一个View,再往View中加入多个子View,加多少个你随意,因为这几个子View是用来作为颜色选择器的,多几个少几个无所谓。然后设置这几个子View的Class为 ColorPiker,接着设置他们的背景颜色,颜色值你随意,因为会把被选中的颜色选择器的背景颜色直接作为参数传出来。
 
 
我们还需要一个画布的区域,再添加一个View,拉伸它,让它覆盖整个空白区域。同样更改它的Class,改为 TouchDrawView。
 
 
我们基本快要完成了。
 
第七步,为刚刚的UI添加一个View Controller类,新建类文件,命名为TouchDrawViewController。修改 .h 文件为:
 
可以看到它实现了ColorPikerDelegate委托,当ColorPiker 被选中后,色值(背景色)会传递到当前类作为画笔的颜色。
 
接下来连接UI和ViewController。
同时打开TouchDrawViewController.xib和TouchDrawViewController.h,Ctrl+Drop UI上的每个色块和画布到TouchDrawViewController.h,完成后TouchDrawViewController.h是这样的:
 
实现TouchDrawViewController.m:
 
在ViewDidLoad方法中,我们把每个ColorPiker的delegate为self,然后实现aColorPikerIsSelected方法,这样色值就可以传递过来了。
 
最后,设置TouchrawViewController为rootController。打开delegate.m文件,修改didFinishLaunchingWithOptions方法为:
 
可以了,运行它吧!

相关源代码:github

在iOS中实现一个简单的画板App的更多相关文章

  1. iOS 中CoreData的简单使用

    原文链接:http://www.jianshu.com/p/4411f507dd9f 介绍:本文介绍的CoreData不在AppDelegate中创建,在程序中新建工程使用,即创建本地数据库,缓存数据 ...

  2. 在eclipse中配置一个简单的spring入门项目

    spring是一个很优秀的基于Java的轻量级开源框架,为了解决企业级应用的复杂性而创建的,spring不仅可用于服务器端开发,从简单性.可测试性和松耦合性的角度,任何java应用程序都可以利用这个思 ...

  3. iOS 中多线程的简单使用

    iOS中常用的多线程操作有( NSThread, NSOperation GCD ) 为了能更直观的展现多线程操作在SB中做如下的界面布局: 当点击下载的时候从网络上下载图片: - (void)loa ...

  4. iOS中动画的简单使用

    iOS中的动画右两大类1.UIView的视图动画2.Layer的动画 UIView的动画也是基于Layer的动画动画的代码格式都很固定 1.UIView动画 一般方式[UIView beginAnim ...

  5. wpf中,一个简单的自定义treeview

    首先创建一个自定义控件,在里面定义好treeview的样式,将本来的三角形的图标变为加号的图标,并且添加节点之间的连线. <UserControl x:Class="TreeViewE ...

  6. 【Unity Shaders】Reflecting Your World —— 在Unity3D中创建一个简单的动态Cubemap系统

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  7. IOS中封装一个View的思路

    一.封装一个View的思路 1.将View内部的业务逻辑(显示内容)封装到View中 2.一般情况下,View的位置应该由父控件来决定,也就是位置不应该固定死在View内部 3.至于View的宽高,根 ...

  8. 用ios做的一个简单的记事本

    #import "ViewController.h" @interface ViewController ()@property (weak, nonatomic) IBOutle ...

  9. 在终端中创建一个简单的mysql表格

    打开终端后输入:/usr/local/MySQL/bin/mysql -u root –p 然后输入密码:***** 创建数据库:create database work; 使用当前数据库:use w ...

随机推荐

  1. list,tuple,dict,set常用方法

    Python中list,tuple,dict,set常用方法 collections模块提供的其它有用扩展类型 from collections import Counter from collect ...

  2. jquery ajax 请求参数详细说明 及 实例

    url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. type: 要求为String类型的参数,请求方式(post或get)默认为get.注意其他http请求方法,例如put和 ...

  3. Linux CentOS6.5下安装Oracle ASM

    Oracle版本:Oracle 11g 1.确定自己的Linux版本: [root@localhost ~]#uname -r 2.6.32-431.el6.x86_64 2.6.32-431.el6 ...

  4. Python开发程序:生产环境下实时统计网站访问日志信息

    日志实时分析系统 生产环境下有需求:要每搁五分钟统计下这段时间内的网站访问量.UV.独立IP等信息,用直观的数据表格表现出来 环境描述: 网站为Nginx服务,系统每日凌晨会对日志进行分割,拷贝到其他 ...

  5. Xcode 7免证书真机调试

    在Xcode 7中,苹果改变了自己在许可权限上的策略,此前Xcode只开放给注册开发者下载,但Xcode 7改变了这种惯有的做法,无需注册开发者账号,仅使用普通的Apple ID就能下载和上手体验.此 ...

  6. iOS CoreAnimate 动画实现

    这里主要讲的是使用CoreAnimate实现所需的动画. 先上官网的介绍:https://developer.apple.com/library/ios/documentation/Cocoa/Con ...

  7. iOS cookie问题

    获取cookie时汉字应转换为UTF8格式

  8. 【leetcode】Unique Binary Search Trees

    Unique Binary Search Trees Given n, how many structurally unique BST's (binary search trees) that st ...

  9. 【Python】我的Python学习笔记【2】【using Python 3】

    ... 1. 在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值, 所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便. 2. ...

  10. URAL 1416 Confidential --最小生成树与次小生成树

    题意:求一幅无向图的最小生成树与最小生成树,不存在输出-1 解法:用Kruskal求最小生成树,标记用过的边.求次小生成树时,依次枚举用过的边,将其去除后再求最小生成树,得出所有情况下的最小的生成树就 ...