一 Modal
- 除了push之外,還有另外一種控制器的切換方式,那就是Modal
- 任何控制器都能通過Modal的形式展示出來
Modal的默認效果:新控制器從屏幕的最底部往上鑽,直到蓋住之前的控制器爲止
以Modal的形式展示控制器
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^)(void))completion
- 關閉當初Modal出來的控制器
- (void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^)(void))completion;
modal:會把新控制器的view添加窗口上,但是不會修改窗口的根控制器
modal:會把新控制器強引用,誰modal,誰就強引用,爲什麼要強引用,如果不強引用,新創建的控制器就會被銷燬,就不能處理modal出來界面的業務邏輯.
- modal原理解析
// 從下往上鑽的動畫
// 首先讓oneVc的view顯示在窗口的底部
oneVc.view.transform = CGAffineTransformMakeTranslation(0, keyWindow.bounds.size.height);
// 動畫,往上移動,還原形變
[UIView animateWithDuration:0.5 animations:^{
// 還原形變
// CGAffineTransformIdentity清空所有的形變,所有的形變參數都是0
oneVc.view.transform = CGAffineTransformIdentity;
}];
// transform:可以用來做控件的形變,平移,縮放,旋轉
二 iOS中的事件
- 事件分3大類型
- 觸摸事件 加速計事件 遠程控制事件
- 響應者對象
- 在iOS中不是任何對象都能處理事件,只有繼承了UIResponder的對象才能接收並處理事件。我們稱之爲“響應者對象”
- UIApplication、UIViewController、UIView都繼承自UIResponder,因此它們都是響應者對象,都能夠接收並處理事件
- 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的觸摸事件處理
- 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對象,使之能夠一直保存該手指在的觸摸位置
- 當手指離開屏幕時,系統會銷燬相應的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;
// 該方法記錄了前一個觸摸點的位置
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// 想讓控件隨着手指移動而移動,監聽手指移動
// 獲取UITouch對象
UITouch *touch = [touches anyObject];
// 獲取當前點的位置
CGPoint curP = [touch locationInView:self];
// 獲取上一個點的位置
CGPoint preP = [touch previousLocationInView:self];
// 獲取它們x軸的偏移量,每次都是相對上一次
CGFloat offsetX = curP.x - preP.x;
// 獲取y軸的偏移量
CGFloat offsetY = curP.y - preP.y;
// 修改控件的形變或者frame,center,就可以控制控件的位置
// 形變也是相對上一次形變(平移)
// CGAffineTransformMakeTranslation:會把之前形變給清空,重新開始設置形變參數
// make:相對於最原始的位置形變
// CGAffineTransform t:相對這個t的形變的基礎上再去形變
// 如果相對哪個形變再次形變,就傳入它的形變
self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}
三 事件的產生和傳遞
UIEvent
- 每產生一個事件,就會產生一個UIEvent對象
- UIEvent:稱爲事件對象,記錄事件產生的時刻和類型
- 常見屬性
// 事件類型 @property(nonatomic,readonly) UIEventType type; @property(nonatomic,readonly) UIEventSubtype subtype; // 事件產生的時間 @property(nonatomic,readonly) NSTimeInterval timestamp;
- 一次完整的觸摸過程,會經歷3個狀態:
觸摸開始:- (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
- 事件的產生和傳遞
- 發生觸摸事件後,系統會將該事件加入到一個由UIApplication管理的事件隊列中
- UIApplication會從事件隊列中取出最前面的事件,並將事件分發下去以便處理,通常,先發送事件給應用程序的主窗口(keyWindow)
- 主窗口會在視圖層次結構中找到一個最合適的視圖來處理觸摸事件,這也是整個事件處理過程的第一步
- 找到合適的視圖控件後,就會調用視圖控件的touches方法來作具體的事件處理
- touchesBegan…
- touchesMoved…
- touchedEnded…
實例:
UIView不接收觸摸事件的三種情況
- 不接收用戶交互:userInteractionEnabled = NO
- 隱藏 hidden = YES
- 透明 alpha = 0.0 ~ 0.01
提示:UIImageView的userInteractionEnabled默認就是NO,因此UIImageView以及它的子控件默認是不能接收觸摸事件的
四 hitTest 方法
- hitTest
- 產生觸摸事件 -> UIApplication事件隊列 -> [UIWindow hitTest] 去尋找最合適的view
// 什麼時候調用:只要事件一傳遞給一個控件就會調用
// 作用:尋找最合適的view給你
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *fitView = [super hitTest:point withEvent:event];
return fitView;
}
使用return nil,可以攔截事件傳遞過程,想讓誰處理事件誰處理事件
- 方法底層實現代碼如下:
// UIApplication -> [UIWindow hitTest:withEvent:]尋找最合適的view告訴系統
// point:當前手指觸摸的點
// point:是方法調用者座標系上的點
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// UIView *fitView = [super hitTest:point withEvent:event];
// 1.判斷下窗口能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
// 2.判斷下點在不在窗口上
// 不在窗口上
if ([self pointInside:point withEvent:event] == NO) return nil;
// 3.從後往前遍歷子控件數組
int count = (int)self.subviews.count;
// 0 1
for (int i = count - 1; i >= 0; i--) {
// 獲取子控件
UIView *childView = self.subviews[i];
// 座標系的轉換,把窗口上的點轉換爲子控件上的點
// 把自己控件上的點轉換成哪個控件上點
CGPoint childP = [self convertPoint:point toView:childView];
UIView *fitView = [childView hitTest:childP withEvent:event];
if (fitView) {// 如果能找到最合適的view
return fitView;
}
}
// 4.沒有比自己更合適的view
return self;
}
- 判斷傳入過來的點在不在方法調用者的座標系上
// point:是方法調用者座標系上的點
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
return NO;
}
五 響應者鏈條
- 觸摸事件處理的詳細過程
- 用戶點擊屏幕後產生一個觸摸事件,經過一系列傳遞過程後,會找到最合適的視圖控件來處理這個事件
- 找到最合適的視圖控件後,就會調用控件的touches方法來作事件處理
- touches方法的默認做法是將事件順着響應者鏈條向上傳遞,將事件交給上一個響應者進行處理
- 響應者鏈條:是由多個響應者對象連接起來的鏈條
- 作用:能很清楚的看見每個響應者之間的聯繫,並且可以讓一個事件多個對象處理。
- 事件傳遞的完整過程
- 1> 先將事件對象由上往下傳遞(由父控件傳遞給子控件),找到最合適的控件來處理這個事件。
- 2> 調用最合適控件的touches….方法
- 3> 如果調用了[super touches….];就會將事件順着響應者鏈條往上傳遞,傳遞給上一個響應者
- 4> 接着就會調用上一個響應者的touches….方法
如何判斷上一個響應者
- 1> 如果當前這個view是控制器的view,那麼控制器就是上一個響應者
- 2> 如果當前這個view不是控制器的view,那麼父控件就是上一個響應者
響應者鏈的事件傳遞過程
- 1.如果view的控制器存在,就傳遞給控制器;如果控制器不存在,則將其傳遞給它的父視圖
- 2.在視圖層次結構的最頂級視圖,如果也不能處理收到的事件或消息,則其將事件或消息傳遞給window對象進行處理
- 3.如果window對象也不處理,則其將事件或消息傳遞給UIApplication對象
- 4.如果UIApplication也不能處理該事件或消息,則將其丟棄
六 手勢識別
- 通過touches方法監聽view觸摸事件,有很明顯的幾個缺點
- 必須得自定義view
- 由於是在view內部的touches方法中監聽觸摸事件,因此默認情況下,無法讓其他外界對象監聽view的觸摸事件
- 不容易區分用戶的具體手勢行爲
- 手勢識別器—-UIGestureRecognizer
- UIGestureRecognizer是一個抽象類,定義了所有手勢的基本行爲,使用它的子類才能處理具體的手勢
UITapGestureRecognizer(敲擊)
UIPinchGestureRecognizer(捏合,用於縮放)
UIPanGestureRecognizer(拖拽)
UISwipeGestureRecognizer(輕掃)
UIRotationGestureRecognizer(旋轉)
UILongPressGestureRecognizer(長按)
- 輕掃手勢
- (void)setUpSwipe
{
// 輕掃
// 一個手勢只能對應一個方向
// 默認輕掃的方向往左
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)];
// 設置輕掃的方向
swipe.direction = UISwipeGestureRecognizerDirectionLeft;
[_imageView addGestureRecognizer:swipe];
// 默認輕掃的方向往右
UISwipeGestureRecognizer *swipeR = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)];
// 設置輕掃的方向
swipeR.direction = UISwipeGestureRecognizerDirectionRight;
[_imageView addGestureRecognizer:swipeR];
}
- (void)swipe:(UISwipeGestureRecognizer *)swipe
{
if (swipe.direction == UISwipeGestureRecognizerDirectionRight) {
// 往右邊輕掃
}else{
// 往左邊輕掃
NSLog(@"%s左邊",__func__);
}
}
- 添加長按手勢
- (void)setUpLongPress
{
// 長按手勢
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
[_imageView addGestureRecognizer:longPress];
}
// 什麼時候調用:長按的時候調用,而且只要手指不離開,拖動的時候會一直調用,手指擡起的時候也會調用
- (void)longPress:(UILongPressGestureRecognizer *)longPress
{
// 注意:在以後開發中,長按手勢一般需要做判斷
if (longPress.state == UIGestureRecognizerStateEnded) {
NSLog(@"%s",__func__);
}
}
- 添加點按手勢,需要添加代理
tap.delegate = self;
- 添加旋轉手勢
- (void)setUpRotation
{
// 旋轉
UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotation:)];
rotation.delegate = self;
[_imageView addGestureRecognizer:rotation];
}
- (void)rotation:(UIRotationGestureRecognizer *)rotation
{
// 獲取的角度是相對於最原始的角度
NSLog(@"%f",rotation.rotation);
// 旋轉圖片
_imageView.transform = CGAffineTransformRotate(_imageView.transform, rotation.rotation);
// 復位,只要想相對於上一次旋轉就復位
rotation.rotation = 0;
}
- 縮放手勢
- (void)setUpPinch
{
// 捏合
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinch:)];
pinch.delegate = self;
[_imageView addGestureRecognizer:pinch];
}
- (void)pinch:(UIPinchGestureRecognizer *)pinch
{
_imageView.transform = CGAffineTransformScale(_imageView.transform, pinch.scale, pinch.scale);
// 復位
pinch.scale = 1;
}
- 如果同時添加縮放和旋轉手勢,代理方法
// Simultaneously:同時
// 是否同時支持多個手勢
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- 拖拽手勢
- (void)pan:(UIPanGestureRecognizer *)pan
{
// 獲取手指平移的偏移量
CGPoint transP = [pan translationInView:_imageView];
_imageView.transform = CGAffineTransformTranslate(_imageView.transform, transP.x, transP.y);
// 復位
[pan setTranslation:CGPointZero inView:_imageView];
}