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個方法處理不一樣的觸摸事件
- (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
提示:touches中存放的都是UITouch對象
UITouch的做用
UITouch的屬性
@property(nonatomic,readonly,retain) UIWindow *window;
@property(nonatomic,readonly,retain) UIView *view;
@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
常見屬性
@property(nonatomic,readonly) UIEventType type;
@property(nonatomic,readonly) UIEventSubtype subtype;
@property(nonatomic,readonly) NSTimeInterval timestamp;
而後用上述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