一個java程序員自學IOS開發之路(十)

2015/11/26node

Day 41程序員

今天開始學起觸摸事件swift

在用戶使用app過程當中,會產生各類各樣的事件數組

iOS中的事件能夠分爲3大類型app

 

響應者對象ide

iOS中不是任何對象都能處理事件,只有繼承了UIResponder的對象才能接收並處理事件。咱們稱之爲「響應者對象ui

UIApplication、UIViewController、UIView都繼承自UIResponder,所以它們都是響應者對象,都可以接收並處理事件atom

UIResponder內部提供瞭如下方法來處理事件spa

  • 觸摸事件

- (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

相關文章
相關標籤/搜索