• 一个java程序员自学IOS开发之路(十)


    2015/11/26

    Day 41

    今天开始学起触摸事件

    在用户使用app过程中,会产生各种各样的事件

    iOS中的事件可以分为3大类型

     

    响应者对象

    iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件。我们称之为“响应者对象

    UIApplication、UIViewController、UIView都继承自UIResponder,因此它们都是响应者对象,都能够接收并处理事件

    UIResponder内部提供了以下方法来处理事件

    • 触摸事件

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;

    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;

    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;

    - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;

     

    • 加速计事件

    - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;

    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;

    - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;

     

    • 远程控制事件

    - (void)remoteControlReceivedWithEvent:(UIEvent *)event;

     

    UIView是UIResponder的子类,可以覆盖下列4个方法处理不同的触摸事件

    • 一根或者多根手指开始触摸view,系统会自动调用view的下面方法

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

     

    • 一根或者多根手指在view上移动,系统会自动调用view的下面方法(随着手指的移动,会持续调用该方法)

    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

     

    • 一根或者多根手指离开view,系统会自动调用view的下面方法

    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

     

    • 触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程,系统会自动调用view的下面方法

    - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

     

    提示:touches中存放的都是UITouch对象

     

    • 当用户用一根触摸屏幕时,会创建一个与手指相关联的UITouch对象
    • 一根手指对应一个UITouch对象

    UITouch的作用

    • 保存着跟手指相关的信息,比如触摸的位置、时间、阶段
    • 当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指在的触摸位置
    • 当手指离开屏幕时,系统会销毁相应的UITouch对象
    • 提示:iPhone开发中,要避免使用双击事件!

    UITouch的属性

    • 触摸产生时所处的窗口

    @property(nonatomic,readonly,retain) UIWindow    *window;

    • 触摸产生时所处的视图

    @property(nonatomic,readonly,retain) UIView      *view;

    • 短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击

    @property(nonatomic,readonly) NSUInteger          tapCount;

    • 记录了触摸事件产生或变化时的时间,单位是秒

    @property(nonatomic,readonly) NSTimeInterval      timestamp;

    • 当前触摸事件所处的状态

    @property(nonatomic,readonly) UITouchPhase        phase;

    UITouch的方法

    - (CGPoint)locationInView:(UIView *)view;

    返回值表示触摸在view上的位置

    这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0))

    调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置

    - (CGPoint)previousLocationInView:(UIView *)view;

    该方法记录了前一个触摸点的位置

    UIEvent

    • 每产生一个事件,就会产生一个UIEvent对象
    • UIEvent:称为事件对象,记录事件产生的时刻和类型

    常见属性

    • 事件类型

    @property(nonatomic,readonly) UIEventType     type;

    @property(nonatomic,readonly) UIEventSubtype  subtype;

    • 事件产生的时间

    @property(nonatomic,readonly) NSTimeInterval  timestamp;

    • UIEvent还提供了相应的方法可以获得在某个view上面的触摸对象(UITouch)
    • 一次完整的触摸过程,会经历3个状态:
    1. 触摸开始:- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    2. 触摸移动:- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    3. 触摸结束:- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    4. 触摸取消(可能会经历):- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
    • 4个触摸事件处理方法中,都有NSSet *touches和UIEvent *event两个参数
    • 一次完整的触摸过程中,只会产生一个事件对象,4个触摸方法都是同一个event参数
    • 如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象
    • 如果这两根手指一前一后分开触摸同一个view,那么view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象
    • 根据touches中UITouch的个数可以判断出是单点触摸还是多点触摸

    然后用上述4个方法做了一个涂鸦app

     

    很简单,先自定义一个view

    @interface YUView : UIView
    - (void)clear;
    - (void)back;
    @end

    两个方法供控制器调用,重置和回退

    @interface YUView ()
    @property (nonatomic, strong) NSMutableArray *totalLines;
    @end

    私有属性是个可变数组,里面装画过的所有线的路径

    实现代码如下

    @implementation YUView
    
    - (NSMutableArray *)totalLines {
    
        if (_totalLines == nil) {
    
            _totalLines = [NSMutableArray array];
    
        }
    
        return _totalLines;
    
    }
    
     
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
        UITouch *touch = [touches anyObject];
    
        CGPoint point = [touch locationInView:touch.view];
    
        UIBezierPath *currentPath = [UIBezierPath bezierPath];
    
        [currentPath moveToPoint:point];
    
        [self.totalLines addObject:currentPath];
    
        [self setNeedsDisplay];
    
    }
    
     
    
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
        UITouch *touch = [touches anyObject];
    
        CGPoint point = [touch locationInView:touch.view];
    
        UIBezierPath *path = [self.totalLines lastObject];
    
        [path addLineToPoint:point];
    
        [self setNeedsDisplay];
    
    }
    
     
    
    - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
        [self touchesMoved:touches withEvent:event];
    
    }
    
     
    
    - (void)drawRect:(CGRect)rect {
    
        [[UIColor blueColor] set];
    
        for (UIBezierPath *path in self.totalLines) {
    
            path.lineWidth = 5;
    
            [path stroke];
    
        }
    
    }
    
     
    
    - (void)clear {
    
        [self.totalLines removeAllObjects];
    
        [self setNeedsDisplay];
    
    }
    
     
    
    - (void)back {
    
        [self.totalLines removeLastObject];
    
        [self setNeedsDisplay];
    
    }
    
    @end

    很简单,只要在触摸事件中存入触摸的点画线就行, 通过 [self setNeedsDisplay];重绘页面

    重置只需清空路径数组,回退只需删除数组中最后一个路径即可

    之前一直看swift文档,实在是太无聊,于是乎我决定把这个涂鸦用swift重构一遍,如下

    class YUView : UIView {
    
        var paths:[UIBezierPath] = []
    
    
        func back() {
    
            self.paths.removeLast()
    
            self.setNeedsDisplay()
    
        }
    
        
    
        func clear() {
    
            self.paths.removeAll()
    
            self.setNeedsDisplay()
    
        }
    
        
    
        override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    
            let touch = touches.first
    
            let point = touch?.locationInView(touch?.view)
    
            let currrentPath = UIBezierPath.init()
    
            currrentPath.moveToPoint(point!)
    
            self.paths.append(currrentPath)
    
            self.setNeedsDisplay()
    
        }
    
        override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
    
            let touch = touches.first
    
            let point = touch?.locationInView(touch?.view)
    
            let path = self.paths.last
    
            path?.lineJoinStyle = .Round
    
            path?.lineCapStyle = .Round
    
            path!.addLineToPoint(point!)
    
            self.setNeedsDisplay()
    
        }
    
        override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    
            self.touchesMoved(touches, withEvent: event)
    
        }
    
        override func drawRect(rect: CGRect) {
    
            UIColor.init(red: 0, green: 0, blue: 1, alpha: 1).set()
    
            for path in self.paths {
    
                path.lineWidth = 8
    
                path.stroke()
    
            }
    
        }
    
    }

    感觉第一次用上swift我就喜欢上它了,代码简洁而且对于我这样非常熟悉.语法的Java程序员非常亲切~

     

    2015/11/27

    Day 42

    今天做了个手势解锁的页面

     

    每个圆圈都是一个button通过改变状态改变图片,通过触摸事件画线就行了。

    button最好别直接用UIButton,自己定义

    @interface YUCircleView : UIButton
    
    @end
    
    @implementation YUCircleView
    
    - (instancetype)initWithCoder:(NSCoder *)aDecoder {
        if (self == [super initWithCoder:aDecoder]) {
            [self setup];
        }
        return self;
    }
    
     
    - (instancetype)initWithFrame:(CGRect)frame {
        if (self == [super initWithFrame:frame]) {
           [self setup];
        }
        return self;
    }
     
    - (void) setup {
        self.userInteractionEnabled = NO;
        [self setImage:[UIImage imageNamed:@"gesture_node_normal"] forState:UIControlStateNormal];
        [self setImage:[UIImage imageNamed:@"gesture_node_highlighted"] forState:UIControlStateSelected];
    }
    @end

    重写那两个初始化方法使按钮不管什么方式创建都设置好图片,不能互动是为了取消按钮的高亮状态

    把按钮放进自定义的view里

    @interface YULockView ()
    @property (nonatomic, strong) NSMutableArray *selectedViews;
    @property (nonatomic, assign) CGPoint currentPoint;
    @end

    这两个私有属性,selectedViews里面保存被选中的按钮,currentPoint保存用户当前触摸的点

    先把九个按钮加上去并且排布好,注意子控件的frame要在layoutSubviews方法中执行并且要调用[super layoutSubviews];

    - (instancetype)initWithCoder:(NSCoder *)aDecoder {
        if (self == [super initWithCoder:aDecoder]) {
            [self setup];
        }
        return self;
    }
    
    - (instancetype)initWithFrame:(CGRect)frame {
        if (self == [super initWithFrame:frame]) {
            [self setup];
        }
        return self;
    }
    
    - (void) setup {
        for (int i = 0; i < 9; i++) {
            YUCircleView *circleView = [YUCircleView buttonWithType:UIButtonTypeCustom];
            circleView.tag = i;
            [self addSubview:circleView];
        }
    }
    
    - (void)layoutSubviews {
        [super layoutSubviews];
        CGFloat viewH = 80;
        CGFloat viewW = 80;
        int totalcol = 3;
    
        for (int i = 0; i < self.subviews.count; i++) {
            YUCircleView *btn = self.subviews[i];
            CGFloat padding = (self.bounds.size.width - totalcol * viewW) / (totalcol + 1);
            CGFloat viewX = padding * (i % totalcol + 1) + i % totalcol * viewW;
            CGFloat viewY = padding * (i / totalcol + 1) + i / totalcol * viewW;
            btn.frame = CGRectMake(viewX, viewY, viewW, viewH);
        }
    }

    在处理触摸事件的四个方法中经常要得到用户当前触摸的点,以及得到用户触摸到的按钮这两个功能,把他们单独抽出来做为方法比较好

    - (CGPoint)getTouchPoint:(NSSet<UITouch *> *)touches {
        UITouch *touch = [touches anyObject];
        CGPoint point = [touch locationInView:touch.view];
        self.currentPoint = point;
        return point;
    }
    
    - (YUCircleView *)getTouchBtn:(CGPoint)point {
        for (YUCircleView *btn in self.subviews) {
            CGFloat d = 50;
            CGFloat frameX = btn.center.x - d * 0.5;
            CGFloat frameY = btn.center.y - d * 0.5;
            if( CGRectContainsPoint(CGRectMake(frameX, frameY, d, d), point)) {
                return btn;
            }
        }
        return nil;
    }

    触碰到圆心周围才判定触碰到按钮

    于是触摸事件的方法如下

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        CGPoint currentPoint = [self getTouchPoint:touches];
        YUCircleView *btn = [self getTouchBtn:currentPoint];
        if (btn && btn.selected == NO) {
            btn.selected = YES;
            [self.selectedViews addObject:btn];
        }
        [self setNeedsDisplay];
    }
    
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        CGPoint currentPoint = [self getTouchPoint:touches];
        YUCircleView *btn = [self getTouchBtn:currentPoint];
        if (btn && btn.selected == NO) {
            btn.selected = YES;
            [self.selectedViews addObject:btn];
        }
        [self setNeedsDisplay];
    }
    
    - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 
        for (YUCircleView *btn in self.selectedViews) {
            btn.selected = NO;
        }
        [self.selectedViews removeAllObjects];
        [self setNeedsDisplay];
    }
    
    - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        [self touchesEnded:touches withEvent:event];
    }
    
    - (void)drawRect:(CGRect)rect {
        if (self.selectedViews.count == 0) return;
        [[UIColor colorWithRed:32/255.0 green:210/255.0 blue:254/255.0 alpha:0.5] set];
        UIBezierPath *path = [UIBezierPath bezierPath];
        for (int i = 0; i < self.selectedViews.count; i++) {
            YUCircleView *btn = self.selectedViews[i];
            if (i == 0) {
                [path moveToPoint:btn.center];
            } else {
                [path addLineToPoint:btn.center];
            }
        }
        [path addLineToPoint:self.currentPoint];
        path.lineCapStyle = kCGLineCapRound;
        path.lineJoinStyle = kCGLineJoinBevel;
        path.lineWidth = 8;
        [path stroke];
    }

    drawRect方法中只要遍历所有选中的按钮再将他们的圆心依次相连就可以了,最后再连到用户当前触摸的点

    这个解锁页面在用户画完后要拿到用户刚才画的路径进行操作的。

    如何得到用户所画的路径呢?

    这时候,初始化各个按钮给按钮绑定的tag就派上用场了,把每个选中的按钮的tag依次串成字符串就是路径啦,一般这个路径是要传出去的,由别人来操作,所以最好使用代理模式

    首先声明代理协议

    @class YULockView;
    @protocol YULockViewDelegate <NSObject>
    @optional
    - (void)lockView:(YULockView *)lockView didFinishPath:(NSString *)path;
    @end

    在YULockView中加入代理属性

    @interface YULockView : UIView
    
    @property(nonatomic, weak) IBOutlet id<YULockViewDelegate> delegate;
    
    @end

    加入IBOutlet是为了我方便的连线设置代理

     在touchesEnded方法中通知代理

    - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        // 通知代理
        if ([self.delegate respondsToSelector:@selector(lockView:didFinishPath:)]) {
            NSMutableString *path = [NSMutableString string];
            for (YUCircleView *btn in self.selectedViews) {
                [path appendFormat:@"%d", (int)btn.tag];
            }
            [self.delegate lockView:self didFinishPath:path];
        }  
    
        for (YUCircleView *btn in self.selectedViews) {
            btn.selected = NO;
        }
        [self.selectedViews removeAllObjects];
        [self setNeedsDisplay];
    }

    然后让控制器实现代理方法

    @interface ViewController () <YULockViewDelegate>
    
    @end
    
    @implementation ViewController
    
    - (void)lockView:(YULockView *)lockView didFinishPath:(NSString *)path {
        NSLog(@"路径为:%@",path);
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    @end

    随便画了一下控制台打印如下

    2015-11-28 22:37:12.537 手势解锁-OC[835:45566] 路径为:13456780

     

    接下来就用swift重新写了一遍

    首先是自定义的button

    class YUCircleView:UIButton {
        override init(frame: CGRect) {
            super.init(frame: frame)
            self.setup()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            self.setup()
        }
    
        func setup() {
            self.userInteractionEnabled = false;
            self.setImage(UIImage(named: "gesture_node_normal"), forState: .Normal)
            self.setImage(UIImage(named: "gesture_node_highlighted"), forState: .Selected)
        }
    }
    

    接下来是自定义view的代码

    public protocol YULockViewDelegate : NSObjectProtocol{
    
        func didFinishPath(lockView:UIView,path:String)
    
    }
    
    class YULockView:UIView {
        weak var delegate:YULockViewDelegate?
        var selectedViews:[YUCircleView] = []
        var currentPoint:CGPoint = CGPointZero
    
        func getTouchPoint(touches: Set<UITouch>) -> CGPoint {
            let touch = touches.first
            let point = touch?.locationInView(touch?.view)
            self.currentPoint = point!
            return point!
        }  
    
        func getTouchBtn(point:CGPoint) -> YUCircleView? {
            for btn in self.subviews {
                let d:CGFloat = 50;
                let frameX = btn.center.x - d * 0.5;
                let frameY = btn.center.y - d * 0.5;
                if( CGRectContainsPoint(CGRectMake(frameX, frameY, d, d), point)) {
                    return btn as? YUCircleView;
                }
            }
            return nil
        }
    
        override func drawRect(rect: CGRect) {
            if self.selectedViews.count == 0 {return}
            UIColor(colorLiteralRed: 32/255.0, green: 210/255.0, blue: 1.0, alpha: 0.5).set()
            let path = UIBezierPath.init()
            for var i = 0; i < self.selectedViews.count; i += 1 {
                let btn = self.selectedViews[i]
                if i == 0 {
                    path.moveToPoint(btn.center)
                } else {
                    path.addLineToPoint(btn.center)
                }
            }
            path.addLineToPoint(self.currentPoint)
            path.lineCapStyle = .Round;
            path.lineJoinStyle = .Bevel;
            path.lineWidth = 8;
            path.stroke()
        }
    
        override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
            let point = self.getTouchPoint(touches)
            let btn = self.getTouchBtn(point)
            if (btn != nil) && (btn!.selected == false) {
                btn!.selected = true
                self.selectedViews.append(btn!)
            }
            self.setNeedsDisplay()
        }
    
        override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
            let point = self.getTouchPoint(touches)
            let btn = self.getTouchBtn(point)
            if (btn != nil) && (btn!.selected == false) {
                btn!.selected = true
                self.selectedViews.append(btn!)
            }
            self.setNeedsDisplay()
        }
    
        override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
            if ((self.delegate?.respondsToSelector(Selector.init("didFinishPath:”))) != nil) {
                var path = ""
                for btn in self.selectedViews {
                    path += "(btn.tag)"
                }
                self.delegate?.didFinishPath(self, path: path)
            }
            for item in self.selectedViews {
                item.selected = false
            }
            self.selectedViews.removeAll()
            self.setNeedsDisplay()
        }
    
        override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
            self.touchesEnded(touches!, withEvent: event)
        } 
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            self.setup()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            self.setup()
        }
    
        func setup() {
            for var i = 0; i < 9; i += 1 {
                let btn = YUCircleView(type: .Custom)
                btn.tag = i
                self.addSubview(btn)
            }
        }
    
        override func layoutSubviews() {
            super.layoutSubviews()
            let viewH:CGFloat = 80
            let viewW:CGFloat = 80
            let totalcol:Int = 3
            for var i = 0; i < self.subviews.count; i += 1 {
                let col = i % totalcol
                let row = i / totalcol
                let paddingX = (self.bounds.size.width - CGFloat(totalcol)  * viewW) / CGFloat(totalcol + 1)
                let paddingY = paddingX
                let viewX = paddingX + CGFloat(col) * (viewW + paddingX)
                let viewY = paddingY + CGFloat(row) * (viewH + paddingY)
                let btn = self.subviews[i] as! YUCircleView
                btn.frame = CGRectMake(viewX, viewY, viewW, viewH)
            }
        }
    }
    

      总体来说思路是一样的,只是代码语法以及风格不同,个人偏爱swift一点,但是swift管类型比较严,不同类型的数据不能做运算,于是在layoutSubviews()方法中转换类型费了点劲,接着就是设置代理和实现代理方法了

    class ViewController: UIViewController, YULockViewDelegate{
        func didFinishPath(lockView: UIView, path: String) {
            print("路径为:"+path)
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
            for item in self.view.subviews {
                if item is YULockView {
                    let lockView = item as! YULockView
                    lockView.delegate = self
                }
            }
        }
    }
    

    随便画了一下控制台输出如下

     路径为:840123675

  • 相关阅读:
    Android WelcomeActivity 启动画更换网络图片
    Android 手机号码格式验证
    Android 身份证号码查询、手机号码查询、天气查询
    Android Http请求框架一:Get 和 Post 请求
    自定义带进度条的WebView , 增加获取web标题和url 回掉
    JavaScript之正則表達式入门
    Spring之WEB模块
    【VBA研究】浮点数计算总是有误差的
    CSDN日报20170403 ——《该不该离职?它说了算!》
    怎样清除浏览器缓存?
  • 原文地址:https://www.cnblogs.com/yu3-/p/5004882.html
Copyright © 2020-2023  润新知