目錄ios
2數據庫
爲了方便管理控制器,ios提供了2種特殊的控制器來管理多控制器
- UINavigationController
- UITabBarController
數組
UINavigationController
pushViewController
凡是導航條下面的scrollView
默認都會有一個64的contentInset
偏移量,若是不想默認,就須要設置self.automaticallyAdjustsScrollViewInset = NO
表示不要自動設置偏移量。這樣下面的滾動表格就從0,0開始了。網絡
app
navgationController
屬性就有值了移走了
,只有pop
纔會刪除控制器,當pop
跳躍移動的時候,那麼這中間的控制器都會被銷燬UINavigationiItem
模型屬性決定的。就是說你這個頁面想展現什麼,就去這個頁面本身設置
backBarButtonItem
:返回按鈕titleView
:中間標題視圖title
:中間文字leftBarButtonItem
:左上角視圖(能夠是文字,圖片,view)
sizeToFit
便可rightBarButtonItem
:右上角視圖/** 全局設置狀態欄白色 全局設置導航欄背景mainColor 全局設置導航欄標題文字顏色白色,微軟雅黑 全局設置導航欄Item顏色 白色 */ - (void)systemSetting { [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent]; [[UINavigationBar appearance] setBarTintColor:mainColor]; [[UINavigationBar appearance] setTintColor:[UIColor whiteColor]]; [[UINavigationBar appearance] setTitleTextAttributes: [NSDictionary dictionaryWithObjectsAndKeys: [UIColor whiteColor], NSForegroundColorAttributeName, [UIFont fontWithName:@"MicrosoftYaHei" size:18.0], NSFontAttributeName, nil]]; }
控制器View的生命週期方法:只要是控制器的生命週期方法,都是以view開頭. 控制器View加載完成時調用 - (void)viewDidLoad { [super viewDidLoad]; } 控制器的View顯示完成時調用 -(void)viewDidAppear:(BOOL)animated{ [super viewDidAppear:animated]; } 控制器的View即將顯示的時候調用 -(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; } 控制器的View徹底消失的時候調用 -(void)viewDidDisappear:(BOOL)animated{ [super viewDidDisappear:animated]; } 控制器的View即將消失的時候調用. -(void)viewWillDisappear:(BOOL)animated{ [super viewWillDisappear:animated]; } 佈局控制器View的子控件完成時調用 -(void)viewDidLayoutSubviews{ [super viewDidLayoutSubviews]; } 將要佈局控制器的View裏面子控件的時候就會調用. -(void)viewWillLayoutSubviews{ [super viewWillLayoutSubviews]; } ARC的生命週期 viewDidLoad->viewWillAppear->viewDidLayoutSubviews->viewDidLayoutSubviews->viewDidAppear-> viewWillDisappear->viewDidDisappear 在非ARC當中. 當前控制器的View即將被銷燬的時候會調用 -(void)viewWillUnload{ [super viewWillUnload]; } 當前控制器的View被銷燬的時候會調用 -(void)viewDidUnload{ [super viewDidUnload]; 清空界面上的數據. self.dataList = nil; } viewDidLoad->viewWillAppear->viewDidLayoutSubviews->viewDidLayoutSubviews->viewDidAppear-> viewWillDisappear->viewDidDisappear->接收到內存警告->viewWillUnload->釋放View->viewDidUnload
ios經常使用的存儲iview
1. plist歸檔 2. Prference偏好設置 3. NSKeyedArchiver歸檔(NSCoding)--> 保存自定義對象 4. SQLite3 5. Core Date(對SQLite3的封裝,有一套庫來操做數據庫)
每個ios應用都有本身的沙盒,而且每個app都是文件隔離,獨立的沙盒,沙盒目錄以下
dom
Documents
存儲着應用運行時須要持久化的數據,iTunes同步設備會備份該目錄,例如遊戲文檔(網絡下載的存這裏直接蘋果退回)
tmp
是保存臨時數據,不備份,系統可能會清除
library/Caches
存儲持久化數據,iTunes不會備份,通常存儲體積大不用備份的非重要數據
library/Preference
保存應用程序全部偏好設置,iTunes會備份,
這些數據若是存儲錯了,那麼打包可能會被蘋果退回來的佈局
- (IBAction)save:(id)sender { 數據存儲是保存在手機裏面的 plist文件存儲通常都是存取字典和數組,直接寫成plist文件,把它存到應用沙盒當中. 只有在ios當中纔有plist存儲,它是ios特有的存儲方式. 獲取沙盒根根路徑,每個應用在手機當中都有一個文件夾,這個方法就是獲取當前應用在手機裏安裝的文件. NSString *homeDir = NSHomeDirectory(); NSLog(@"homeDir = %@",homeDir); 在某個範圍內搜索文件夾的路徑. directory:獲取哪一個文件夾 domainMask:在哪一個路徑下搜索 expandTilde:是否展開路徑. 這個方法獲取出的結果是一個數組.由於有能夠搜索到多個路徑. NSArray *array = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 在這裏,咱們指定搜索的是Cache目錄,因此結果只有一個,取出Cache目錄 NSString *cachePath = array[0]; 拼接文件路徑 NSString *filePathName = [cachePath stringByAppendingPathComponent:@"agePlist.plist"]; 想要把這個字典存儲爲plist文件. 直接把字典寫入到沙盒當中 用字典寫, plist文件當中保存的是字典. NSDictionary *dict = @{@"age" : @18,@"name" : @"gaowei"}; 獲取沙盒路徑 ToFile:要寫入的沙盒路徑 [dict writeToFile:filePathName atomically:YES]; 用數組寫,plist文件當中保存的類型是數組. NSArray *dataArray = @[@56,@"asdfa"]; 獲取沙盒路徑 ToFile:要寫入的沙盒路徑 [dataArray writeToFile:filePathName atomically:YES]; } 讀取數據 - (IBAction)reader:(id)sender { 這個方法獲取出的結果是一個數組.由於有能夠搜索到多個路徑. NSArray *array = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 在這裏,咱們指定搜索的是Cache目錄,因此結果只有一個,取出Cache目錄 NSString *cachePath = array[0]; 拼接文件路徑 NSString *filePathName = [cachePath stringByAppendingPathComponent:@"agePlist.plist"]; 從文件當中讀取字典, 保存的plist文件就是一個字典,這裏直接填寫plist文件所存的路徑 NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePathName]; 若是保存的是一個數組.那就經過數組從文件當中加載. NSArray *dataArray = [NSArray arrayWithContentsOfFile:filePathName]; NSLog(@"%@",dataArray); }
- (IBAction)save:(id)sender { 偏好設置NSUserDefaults 底層就是封閉了一個字典,利用字典的方式生成plist文件 好處:不須要關心文件名(它會自動生成)快速進行鍵值對存儲. NSUserDefaults *defautls = [NSUserDefaults standardUserDefaults]; [defautls setObject:@"gaowei" forKey:@"name"]; [defautls setBool:YES forKey:@"isBool"]; [defautls setInteger:5 forKey:@"num"]; //同步,當即寫入文件. [defautls synchronize]; } - (IBAction)reader:(id)sender { 存是用什麼key存的, 讀的時候就要用什麼key值取 存的時候使用的什麼類型,取的時候也要用什麼類型. NSString *str = [[NSUserDefaults standardUserDefaults] objectForKey:@"name"]; BOOL isBool = [[NSUserDefaults standardUserDefaults] boolForKey:@"isBool"]; NSInteger num = [[NSUserDefaults standardUserDefaults] integerForKey:@"num"]; NSLog(@"name =%@-isBool=%d-num=%ld",str,isBool,num); }
保存數據 - (IBAction)save:(id)sender { 歸檔通常都是保存自定義對象的時候,使用歸檔.由於plist文件不可以保存自定義對象. 若是一個字典當中保存有自定義對象,若是把這個字典寫入到文件當中,它是不會生成plist文件的. Persion *persion = [[Persion alloc] init]; persion.name = @"gaowei"; persion.age = 18; 獲取沙盒臨時目錄 NSString *tempPath = NSTemporaryDirectory(); NSString *filePath = [tempPath stringByAppendingPathComponent:@"persion.data"]; archiveRootObject這個方法底層會去調用保存對象的encodeWithCoder方法,去詢問要保存這個對象的哪些屬性. 因此要實現encodeWithCoder方法, 告訴要保存這個對象的哪些屬性. [NSKeyedArchiver archiveRootObject:persion toFile:filePath]; } 讀取數據 - (IBAction)reader:(id)sender { 獲取沙盒臨時目錄 NSString *tempPath = NSTemporaryDirectory(); NSString *filePath = [tempPath stringByAppendingPathComponent:@"persion.data"]; NSKeyedUnarchiver會調用initWithCoder這個方法,來讓你告訴它去獲取這個對象的哪些屬性. 因此咱們在保存的對象當中實現initWithCoder方法. Persion *persion = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; NSLog(@"name=%@---age=%d",persion.name,persion.age); } 要保存的對象 #import <Foundation/Foundation.h> 要遵照<NSCoding>協議 @interface Persion : NSObject<NSCoding> @property (nonatomic, strong) NSString *name; @property (nonatomic, assign) int age; @end archiveRootObject這個方法底層會去調用保存對象的encodeWithCoder方法,去詢問要保存這個對象的哪些屬性. 只有遵照了NSCoding協議以後纔可以實現這個方法. -(void)encodeWithCoder:(NSCoder *)encode{ [encode encodeObject:self.name forKey:@"name"]; [encode encodeInt32:self.age forKey:@"age"]; } NSKeyedUnarchiver會調用initWithCoder這個方法,來讓你告訴它去獲取這個對象的哪些屬性. initWithCoder何時調用:解析一個文件的時候就會調用. -(instancetype)initWithCoder:(NSCoder *)decoder{ 這個地方爲何沒有[super initWithCoder] 是由於它的父類沒有遵照NSCoding協議 if (self = [super init]) { 要給它裏面的屬性進行賦值,外界取得對象時訪問該屬性,裏面纔會用值. self.age = [decoder decodeInt32ForKey:@"age"]; self.name = [decoder decodeObjectForKey:@"name"]; } return self; }
49
, selecyedIndex
屬性控制當前顯示控制器,而且也有一個數組childViewControllers
,可是添加的時候不是棧的形式了,當選中(顯示)別的控制器的時候,不顯示的控制器仍是一直在的,由於都在數組裏,沒釋放的。UITabBarItem
模型控制,其中有title,image,selectedImage,badgeValue
等屬性。present 和 dismiss
來完成。push
通常是有關係的兩個頁面,modal通常註冊啊這個那個均可以用。在作動畫的時候,通常會對View作平移,旋轉,縮放
等操做,就用到transform
字體
- CGAffineTransformTranslate - CGAffineTransformRotate - CGAffineTransformScale
1.ios的事件分爲三大類,觸摸,加速計,遠程控制事件
,分別是觸摸,搖動手機,控制耳機按鈕等。動畫
觸摸
:ios中不是任何對象都能處理事件,只有繼承了UIResponder的對象才能響應,稱爲響應者對象。UIApplication,UIViewController,UIView
都繼承自UIResponder,都能接收而且處理事件。
//當開始觸摸屏幕的時候調用 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ NSLog(@"%s",__func__); } //觸摸時開始移動時調用(移動時會持續調用) //NSSet:無序 //NSArray:有序 -(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ //NSLog(@"%s",__func__); //作UIView拖拽 UITouch *touch = [touches anyObject]; //求偏移量 = 手指當前點的X - 手指上一個點的X CGPoint curP = [touch locationInView:self]; CGPoint preP = [touch previousLocationInView:self]; NSLog(@"curP====%@",NSStringFromCGPoint(curP)); NSLog(@"preP====%@",NSStringFromCGPoint(preP)); CGFloat offsetX = curP.x - preP.x; CGFloat offsetY = curP.y - preP.y; //平移 self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY); } //當手指離開屏幕時調用 -(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ NSLog(@"%s",__func__); } //當發生系統事件時就會調用該方法(電話打入,自動關機) -(void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ NSLog(@"%s",__func__); }
2.事件的傳遞:touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
方法中的touches
記錄了你當前觸摸的點,上一次觸摸的點,你觸摸的當前控制器或者view,而event
就保存着事件,裏面保存着當前發生的事件的時間,類型等。
- - -
當發生一個觸摸事件,系統會將該事件加入一個由UIApplication
管理事件的隊列(先進先出),而後拿出最前面的事件(也就是先觸摸的),而後交給主窗口keyWindow
,主窗口會根據視圖層次結構找到一個最合適的視圖來處理觸摸事件,找到了就調用touch方法了。
UIAppcation
-> UIWindow
-> 父控件
-> 一層層到子控件
。若是父控件沒法接受觸摸,那麼子控件也沒法接收觸摸。若是父控件隱藏,子控件也隱藏。若是父控件aplha = 0,子控件也是0.
3.UIView不接收觸摸的三種狀況
- userIntercationEnable = NO - hidden = YES - alpha = 0.0 ~ 0.01 - UIImageView默認用戶交互是No
4.如何尋找最適合View?
先看本身可否接收事件,再看觸摸點在不在本身身上,而後從後往前遍歷子控件,若是沒有合適的就是本身最合適處理。若是用代碼來尋找,就要調用hitTest
方法。(開發中沒用過 )
//做用:去尋找最適合的View //何時調用:當一個事件傳遞給當前View,就會調用. -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ NSLog(@"%s",__func__); return [super hitTest:point withEvent:event]; }
5 事件觸摸
在產生一個事件時,系統會將該事件加入到一個由UIApplication管理的事件隊列中,
UIApplication會從事件隊列中取出最前面的事件,將它傳遞給先發送事件給應用程序的主窗口.
主窗口會調用hitTest方法尋找最適合的視圖控件,找到後就會調用視圖控件的touches方法來作具體的事情.
當調用touches方法,它的默認作法, 就會將事件順着響應者鏈條往上傳遞,
傳遞給上一個響應者,接着就會調用上一個響應者的touches方法
如何去尋找上一個響應者? 1.若是當前的View是控制器的View,那麼控制器就是上一個響應者. 2.若是當前的View不是控制器的View,那麼它的父控件就是上一個響應者. 3.在視圖層次結構的最頂級視圖,若是也不能處理收到的事件或消息,則其將事件或消息傳遞給window對象進行處理 4.若是window對象也不處理,則其將事件或消息傳遞給UIApplication對象 5.若是UIApplication也不能處理該事件或消息,則將其丟棄
6.手勢識別
手勢識別器UIGrestreRecognizer
是一個抽象類,使用它的子類才能處理手勢,有點按,長按,輕掃,拖動,旋轉,捏合
六種。
添加點按手勢 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)]; 手勢也能夠設置代理 tap.delegate = self; 添加手勢 [self.imageV addGestureRecognizer:tap]; 代理方法: 是否容許接收手指 -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{ 讓圖片的左邊不能夠點擊, 獲取當前手指所在的點.是在圖片的左邊仍是在圖片的右邊. CGPoint curP = [touch locationInView:self.imageV]; if (curP.x > self.imageV.bounds.size.width * 0.5) { 在圖片的右側 return YES; }else{ 在圖片的左側 return NO; } return YES; } 添加長按手勢 UILongPressGestureRecognizer *longP = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longP:)]; [self.imageV addGestureRecognizer:longP]; 當長按時調用. 這個方法會調用不少次, 當手指長按在上面不鬆,來回移動時,會持續調用. 因此要判斷它的狀態. - (void)longP:(UILongPressGestureRecognizer *)longP{ if(longP.state == UIGestureRecognizerStateBegan){ NSLog(@"開始長按"); }else if(longP.state == UIGestureRecognizerStateChanged){ NSLog(@"長按時手指移動"); }else if(longP.state == UIGestureRecognizerStateEnded){ NSLog(@"手指離開屏幕"); } } 添加輕掃手勢 UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)]; 輕掃手勢默認是向右邊稱輕掃 能夠設置輕掃的方法. 一個輕掃手勢只能設置一個方法的輕掃.想要讓它有多個方向的手勢,必須得要設置的 swipe.direction = UISwipeGestureRecognizerDirectionLeft; [self.imageV addGestureRecognizer:swipe]; 添加輕掃手勢 UISwipeGestureRecognizer *swipe2 = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)]; 輕掃手勢默認是向右邊稱輕掃 能夠設置輕掃的方法. 一個輕掃手勢只能設置一個方法的輕掃.想要讓它有多個方向的手勢,必須得要設置的 swipe2.direction = UISwipeGestureRecognizerDirectionUp; [self.imageV addGestureRecognizer:swipe2]; - (void)swipe:(UISwipeGestureRecognizer *)swipe{ 判斷的輕掃的方向 if (swipe.direction == UISwipeGestureRecognizerDirectionLeft) { NSLog(@"向左輕掃"); }else if(swipe.direction == UISwipeGestureRecognizerDirectionUp){ NSLog(@"向上輕掃"); } } 添加平移手勢 UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)]; [self.imageV addGestureRecognizer:pan]; 當手指拖動時調用 - (void)pan:(UIPanGestureRecognizer *)pan{ 拖動手勢也有狀態 if(pan.state == UIGestureRecognizerStateBegan){ 開始拖動 }else if(pan.state == UIGestureRecognizerStateChanged){ 可能獲取不當前手指移動的舉例 是相對於最原始的點 CGPoint transP = [pan translationInView:self.imageV]; 會清空上一次的形變 self.imageV.transform = CGAffineTransformMakeTranslation(transP.x,transP.y); self.imageV.transform = CGAffineTransformTranslate(self.imageV.transform, transP.x, transP.y); 復位,讓它相對於上一次. [pan setTranslation:CGPointZero inView:self.imageV]; }else if(pan.state == UIGestureRecognizerStateEnded){ 結束拖動 } } 添加捏合手勢 UIPinchGestureRecognizer *pinGes = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinGes:)]; 設置代理使其可以同時支持多個手勢 pinGes.delegate = self; [self.imageV addGestureRecognizer:pinGes]; 旋轉時調用 - (void)pinGes:(UIPinchGestureRecognizer *)pin{ self.imageV.transform = CGAffineTransformScale(self.imageV.transform, pin.scale, pin.scale); 復位 [pin setScale:1]; } 添加旋轉手勢 UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotation:)]; 設置代理使其可以同時支持多個手勢 rotation.delegate = self; [self.imageV addGestureRecognizer:rotation]; 當手指開始旋轉時調用. - (void)rotation:(UIRotationGestureRecognizer *)rotation{ self.imageV.transform = CGAffineTransformRotate(self.imageV.transform, rotation.rotation); 復位. [rotation setRotation:0]; }
能夠添加多個手勢,例如又旋轉又放大又捏合(支持代理,實現方法就行,要找一找,方法比較多,是一個容許支持多手勢的代理方法),
Make by:弓_雖_子 第一步:搭建界面 - (void)viewDidLoad { [super viewDidLoad]; 搭建界面 [self setUpView]; } - (void)setUpView{ 添加左邊的View UIView *leftV = [[UIView alloc] initWithFrame:self.view.bounds]; 左邊藍色 leftV.backgroundColor = [UIColor blueColor]; [self.view addSubview:leftV]; 添加右邊的View UIView *rightV = [[UIView alloc] initWithFrame:self.view.bounds]; 右邊縁色 rightV.backgroundColor = [UIColor greenColor]; self.rightV = rightV; [self.view addSubview:rightV]; 添加中間的View(中間一個最後添加,顯示到最外面.) UIView *mainV = [[UIView alloc] initWithFrame:self.view.bounds]; 中間紅色 mainV.backgroundColor = [UIColor redColor]; self.mainV = mainV; [self.view addSubview:mainV]; } 第二步.添加手勢.可以讓中間的紅色View左右移動,要在控制器View加載完成時就要添加View - (void)viewDidLoad { [super viewDidLoad]; 搭建界面 [self setUpView]; 拖動手勢 UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)]; 添加手勢 [self.mainV addGestureRecognizer:pan]; } 實現手勢方法: 當手指拖動時調用. -(void)pan:(UIPanGestureRecognizer *)pan{ 獲取手指在屏幕上面的偏移量 CGPoint transP = [pan translationInView:self.mainV]; 在這裏爲何不用Transform,是由於咱們移動時,要改變的尺寸大小.用Transform只能改變它的位置. self.mainV.transform = CGAffineTransformTranslate(self.mainV.transform, transP.x, 0); 計算mainV的Frame 單獨抽出一個方法來計算mainV的frame.由於要計算它的Y值和高度.代碼會不少.因此單獨抽出一個方法 self.mainV.frame = [self frameWithOffsetX:transP.x]; 每次移動時判斷當前MainV的x值是大於0仍是小於0.若是是大於0 , 顯示左邊,小於0 顯示右邊 if (self.mainV.frame.origin.x > 0) { self.rightV.hidden = YES; }else if(self.mainV.frame.origin.x < 0){ self.rightV.hidden = NO; } 注意要作復位 [pan setTranslation:CGPointZero inView:self.mainV]; } 最大Y值爲100 #define maxY 100 根據偏移量計算mainV的frame. - (CGRect)frameWithOffsetX:(CGFloat)offsetX{ 取出最原始的Frame CGRect frame = self.mainV.frame; frame.origin.x += offsetX; 獲取屏幕的寬度 (計算Y值若是下圖:找最大值.當Main.x拖動最大的時候,Main.y值也最大.main.x最大爲屏幕的寬度) 設定一個最大Y值MaxY爲100,正好.當max.x爲屏幕的寬度時,最大Y等於100 因此Y值等於 main.y = main.x * maxY / ScreenW; 100 = 375 * 100 / 375;) 有可能frame.origin.x有多是小於0,小於0的話, 得出的Y值就會小於0,小於0就會出現, 紅色的View向上走. 對結果取絕對值. frame.origin.y = fabs(frame.origin.x * maxY / screenW); 計算frame的高度 (當前Main的高度等於屏幕的高度減去兩倍的Y值.) frame.size.height = screenH - 2 * frame.origin.y; 返回計算好的frame. return frame; } 第三步:當手指鬆開時作到自動定位. MainV定位到右側的X值 #define targetR 275 MainV定位到右側的X值 #define targetL -275 當手指拖動時調用. -(void)pan:(UIPanGestureRecognizer *)pan{ 獲取手指在屏幕上面的偏移量 CGPoint transP = [pan translationInView:self.mainV]; 在這裏爲何不用Transform,是由於咱們移動時,要改變的尺寸大小.用Transform只能改變它的位置. self.mainV.transform = CGAffineTransformTranslate(self.mainV.transform, transP.x, 0); 計算mainV的Frame 單獨抽出一個方法來計算mainV的frame.由於要計算它的Y值和高度.代碼會不少.因此單獨抽出一個方法 self.mainV.frame = [self frameWithOffsetX:transP.x]; 每次移動時判斷當前MainV的x值是大於0仍是小於0.若是是大於0 , 顯示左邊,小於0 顯示右邊 if (self.mainV.frame.origin.x > 0) { self.rightV.hidden = YES; }else if(self.mainV.frame.origin.x < 0){ self.rightV.hidden = NO; } 判斷手指的狀態 if(pan.state == UIGestureRecognizerStateEnded){ 當手指鬆開時進入執行 記錄最終判斷結果後.定位的值. CGFloat target = 0; 當手指鬆開,要判斷MainV的x值是否大於屏幕的一半.若是大於屏幕一半時, 自動定位到右邊一個位置. if (self.mainV.frame.origin.x > screenW * 0.5) { target = targetR; }else if(CGRectGetMaxX(self.mainV.frame) < screenW * 0.5){ 當手指鬆開,要判斷MainV的最大的X值是否小於屏幕的一半.若是小於屏幕的一半時, 自動定位到左邊的位置. target = targetL; } 最終定位的x值 - 當前的main.x的值. 求出便宜量.使其定位 CGFloat offsetX = target - self.mainV.frame.origin.x; 根據便宜量設置mainV的frame值 CGRect frame = [self frameWithOffsetX:offsetX]; [UIView animateWithDuration:0.25 animations:^{ 伴隨動畫設置frame self.mainV.frame = frame; }]; } 注意要作復位 [pan setTranslation:CGPointZero inView:self.mainV]; }