国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > iOS > 正文

iOS 一次性解決導(dǎo)航欄的所有問題

2019-11-09 14:43:13
字體:
供稿:網(wǎng)友

系統(tǒng)默認(rèn)導(dǎo)航欄的返回按鈕和返回方式

在默認(rèn)情況下,導(dǎo)航欄返回按鈕長這個樣子

導(dǎo)航欄默認(rèn)返回按鈕

導(dǎo)航欄左上角的返回按鈕,其文本默認(rèn)為上一個ViewController的標(biāo)題,如果上一個ViewController沒有標(biāo)題,則為Back(中文環(huán)境下為“返回”)。

在默認(rèn)情況下,導(dǎo)航欄返回的點擊交互和滑動交互如下

默認(rèn)導(dǎo)航欄交互

這些東西不需要任何設(shè)置和操作,因此也沒有其他需要說明的地方。

自定義左上角的返回按鈕

絕大多數(shù)情況下,我們都需要根據(jù)產(chǎn)品需求自定義左上角的返回按鈕,雖然這對大多數(shù)開發(fā)者來說不是什么難事,但依然有幾個問題值得注意。

替換左上角返回按鈕

替換返回按鈕非常簡單,只需要在ViewController中創(chuàng)建一個UIBarButtonItem和一張圖片,并為按鈕添加相應(yīng)的點擊事件即可,代碼如下

- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.UIButton * leftBtn = [UIButton buttonWithType:UIButtonTypeSystem];leftBtn.frame = CGRectMake(0, 0, 25,25);[leftBtn setBackgroundImage:[UIImage imageNamed:@"nav_back"] forState:UIControlStateNormal];[leftBtn addTarget:self action:@selector(leftBarBtnClicked:) forControlEvents:UIControlEventTouchUpInside];self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithCustomView:leftBtn];}- (void)leftBarBtnClicked:(UIButton *)btn{ [self.navigationController popViewControllerAnimated:YES];}

我們來看一眼效果

替換返回按鈕
調(diào)整按鈕位置

我們可以看到,上面的按鈕是有點偏右的,那如果我們想調(diào)整按鈕的位置該怎么做呢?設(shè)置Frame顯然是行不通的,因為導(dǎo)航欄的NavigationItem是個比較特殊的View,我們無法通過簡單的調(diào)整Frame來的調(diào)整左右按鈕的位置。但是在蘋果提供的UIButtonBarItem中有個叫做UIBarButtonSystemItemFixedSpace的控件,利用它,我們就可以輕松調(diào)整返回按鈕的位置。具體使用方法如下

//創(chuàng)建返回按鈕UIButton * leftBtn = [UIButton buttonWithType:UIButtonTypeSystem];leftBtn.frame = CGRectMake(0, 0, 25,25);[leftBtn setBackgroundImage:[UIImage imageNamed:@"icon_back"] forState:UIControlStateNormal];[leftBtn addTarget:self action:@selector(leftBarBtnClicked:) forControlEvents:UIControlEventTouchUpInside];UIBarButtonItem * leftBarBtn = [[UIBarButtonItem alloc]initWithCustomView:leftBtn];;//創(chuàng)建UIBarButtonSystemItemFixedSpaceUIBarButtonItem * spaceItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];//將寬度設(shè)為負(fù)值spaceItem.width = -15;//將兩個BarButtonItem都返回給NavigationItemself.navigationItem.leftBarButtonItems = @[spaceItem,leftBarBtn];

我們來看一眼效果

調(diào)整返回按鈕位置

可以看到,我們的返回按鈕已經(jīng)緊靠著屏幕邊緣。

這個方法同樣適用于調(diào)整導(dǎo)航欄右側(cè)的按鈕

讓滑動返回手勢生效

如果使用自定義的按鈕去替換系統(tǒng)默認(rèn)返回按鈕,會出現(xiàn)滑動返回手勢失效的情況。解決方法也很簡單,只需要重新添加導(dǎo)航欄的interactivePopGestureRecognizerdelegate即可。首先為ViewContoller添加UIGestureRecognizerDelegate協(xié)議

然后設(shè)置代理

self.navigationController.interactivePopGestureRecognizer.delegate = self;

至此,我們已經(jīng)將返回按鈕替換為我們的自定義按鈕,并使滑動返回重新生效。接下來,我們繼續(xù)來解決交互上的問題。

全屏滑動返回

這個一個很常見的需求,網(wǎng)上解決方案也很多,這里將本人常用的方法貼到這里。僅供參考實現(xiàn)全屏滑動返回僅需在導(dǎo)航欄給導(dǎo)航欄添加UIGestureRecognizerDelegate協(xié)議,并在ViewDidLoad中寫入如下代碼

// 獲取系統(tǒng)自帶滑動手勢的target對象id target = self.interactivePopGestureRecognizer.delegate;// 創(chuàng)建全屏滑動手勢,調(diào)用系統(tǒng)自帶滑動手勢的target的action方法UipanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:@selector(handleNavigationTransition:)];// 設(shè)置手勢代理,攔截手勢觸發(fā)pan.delegate = self;// 給導(dǎo)航控制器的view添加全屏滑動手勢[self.view addGestureRecognizer:pan];// 禁止使用系統(tǒng)自帶的滑動手勢self.interactivePopGestureRecognizer.enabled = NO;

我們來看一眼效果(注意鼠標(biāo)位置)

全屏滑動返回.gif

成功

這種方法的原理其實很簡單,其實就是自定義一個全屏滑動手勢,并將滑動事件設(shè)置為系統(tǒng)滑動事件,然后禁用系統(tǒng)滑動手勢即可。handleNavigationTransition就是系統(tǒng)滑動的方法,雖然系統(tǒng)并未提供接口,但是我們我們可以通過runtime找到這個方法,因此直接調(diào)用即可。兩位,不必?fù)?dān)心什么私有API之類的問題,蘋果如果按照方法名去判斷是否使用私有API,那得誤傷多少App。

NavigationBar切換動畫的“終極解決方案”

本部分文字代碼都較多,不想看這么多廢話的同學(xué)請直接翻到末尾,文末附有下載地址,導(dǎo)入項目后,繼承即可生效。

在改變了導(dǎo)航欄樣式,實現(xiàn)了全屏滑動返回之后,我們有了一個看起來還不錯的導(dǎo)航欄。但是我們滑動時的切換依然是系統(tǒng)自帶的動畫,如果遇到前一個界面的NavigationBar為透明或前后兩個Bar顏色不一樣,這種漸變式的動畫看起來就會不太友好,尤其當(dāng)前后兩個界面其中一個界面的NavigationBar為透明或隱藏時,其效果更是慘不忍睹。

這個問題,其實很多App,比如天貓、美團等都通過一種“整體返回”的效果來解決這個問題。效果如下:

整體滑動返回

這種解決方案等于將兩個NavigationBar獨立開來,因此可以相對完美的解決導(dǎo)航欄滑動切換中的種種Bug。接下來,我們來看看如何實現(xiàn)這種效果。

基本原理

以我個人的認(rèn)知,實現(xiàn)這個效果有三種基本思路:

使用UINavigationController自帶的setNavigationBarHidden: animated:方法來實現(xiàn),每次push或pop時,在當(dāng)前控制器的viewWillDisappear:中設(shè)置隱藏,在要跳轉(zhuǎn)的控制器的viewWillAppear:中設(shè)置導(dǎo)航欄顯示。在每次Push前對當(dāng)前頁面進行截圖并保存到數(shù)組,Pop時取數(shù)組最后一個元素顯示,滑動結(jié)束后調(diào)用系統(tǒng)Pop方法并刪除最后一張截圖。使用iOS 7之后開放的,UIViewControllerAnimatedTransitioning協(xié)議,來實現(xiàn)自定義導(dǎo)航欄轉(zhuǎn)場動畫及交互。

以上三種方法,方法一十分繁瑣,而且會有很多莫名其妙的BUG,直接pass。

在iOS的交互中,push一般通過按鈕的點擊事件或View的tap事件觸發(fā),而pop則可能通過事件觸發(fā),也可能通過右滑手勢觸發(fā)。因此,我們將這個我們要實現(xiàn)的動畫效果分為交互效果和無交互效果兩種,下面我們將使用方法2和方法3提供的思路,分別實現(xiàn)這兩種效果,這樣就能較為完美的解決Push和Pop的動畫問題。

實現(xiàn)交互動畫效果

準(zhǔn)備需要使用的數(shù)組及手勢
#define ScreenWidth [UIScreen mainScreen].bounds.size.width#define ScreenHeight [UIScreen mainScreen].bounds.size.height@interface LTNavigationController ()<UIGestureRecognizerDelegate>@PRoperty(strong,nonatomic)UIImageView * screenshotImgView;@property(strong,nonatomic)UIView * coverView;@property(strong,nonatomic)NSMutableArray * screenshotImgs;@property(strong,nonatomic)UIPanGestureRecognizer *panGestureRec;@end@implementation LTNavigationController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.// 1,創(chuàng)建Pan手勢識別器,并綁定監(jiān)聽方法_panGestureRec = [[UIScreenEdgePanGestureRecognizer alloc]initWithTarget:self action:@selector(panGestureRec:)];_panGestureRec.edges = UIRectEdgeLeft;// 為導(dǎo)航控制器的view添加Pan手勢識別器[self.view addGestureRecognizer:_panGestureRec];// 2.創(chuàng)建截圖的ImageView_screenshotImgView = [[UIImageView alloc] init];// app的frame是包括了狀態(tài)欄高度的frame_screenshotImgView.frame = CGRectMake(0, 0, ScreenWidth, ScreenHeight);// 3.創(chuàng)建截圖上面的黑色半透明遮罩_coverView = [[UIView alloc] init];// 遮罩的frame就是截圖的frame_coverView.frame = _screenshotImgView.frame;// 遮罩為黑色_coverView.backgroundColor = [UIColor blackColor];// 4.存放所有的截圖數(shù)組初始化_screenshotImgs = [NSMutableArray array];}
實現(xiàn)手勢的相應(yīng)事件
// 響應(yīng)手勢的方法- (void)panGestureRec:(UIPanGestureRecognizer *)panGestureRec{// 如果當(dāng)前顯示的控制器已經(jīng)是根控制器了,不需要做任何切換動畫,直接返回if(self.visibleViewController == self.viewControllers[0]) return;// 判斷pan手勢的各個階段switch (panGestureRec.state) {    case UIGestureRecognizerStateBegan:        // 開始拖拽階段        [self dragBegin];        break;    case UIGestureRecognizerStateEnded:        // 結(jié)束拖拽階段        [self dragEnd];        break;    default:        // 正在拖拽階段        [self dragging:panGestureRec];        break;}}#pragma mark 開始拖動,添加圖片和遮罩- (void)dragBegin{// 重點,每次開始Pan手勢時,都要添加截圖imageview 和 遮蓋cover到window中[self.view.window insertSubview:_screenshotImgView atIndex:0];[self.view.window insertSubview:_coverView aboveSubview:_screenshotImgView];// 并且,讓imgView顯示截圖數(shù)組中的最后(最新)一張截圖_screenshotImgView.image = [_screenshotImgs lastObject];//_screenshotImgView.transform = CGAffineTransformMakeTranslation(ScreenWidth, 0);}// 默認(rèn)的將要變透明的遮罩的初始透明度(全黑)#define kDefaultAlpha 0.6// 當(dāng)拖動的距離,占了屏幕的總寬高的3/4時, 就讓imageview完全顯示,遮蓋完全消失#define kTargetTranslateScale 0.75#pragma mark 正在拖動,動畫效果的精髓,進行位移和透明度變化- (void)dragging:(UIPanGestureRecognizer *)pan{// 得到手指拖動的位移CGFloat offsetX = [pan translationInView:self.view].x;// 讓整個view都平移     // 挪動整個導(dǎo)航viewif (offsetX > 0) {    self.view.transform = CGAffineTransformMakeTranslation(offsetX, 0);  }// 計算目前手指拖動位移占屏幕總的寬高的比例,當(dāng)這個比例達到3/4時, 就讓imageview完全顯示,遮蓋完全消失double currentTranslateScaleX = offsetX/self.view.frame.size.width;if (offsetX < ScreenWidth) {    _screenshotImgView.transform = CGAffineTransformMakeTranslation((offsetX - ScreenWidth) * 0.6, 0);}// 讓遮蓋透明度改變,直到減為0,讓遮罩完全透明,默認(rèn)的比例-(當(dāng)前平衡比例/目標(biāo)平衡比例)*默認(rèn)的比例double alpha = kDefaultAlpha - (currentTranslateScaleX/kTargetTranslateScale) * kDefaultAlpha;_coverView.alpha = alpha;}#pragma mark 結(jié)束拖動,判斷結(jié)束時拖動的距離作相應(yīng)的處理,并將圖片和遮罩從父控件上移除- (void)dragEnd{// 取出挪動的距離CGFloat translateX = self.view.transform.tx;// 取出寬度CGFloat width = self.view.frame.size.width;if (translateX <= 40) {    // 如果手指移動的距離還不到屏幕的一半,往左邊挪 (彈回)    [UIView animateWithDuration:0.3 animations:^{        // 重要~~讓被右移的view彈回歸位,只要清空transform即可辦到        self.view.transform = CGAffineTransformIdentity;        // 讓imageView大小恢復(fù)默認(rèn)的translation        _screenshotImgView.transform = CGAffineTransformMakeTranslation(-ScreenWidth, 0);        // 讓遮蓋的透明度恢復(fù)默認(rèn)的alpha 1.0        _coverView.alpha = kDefaultAlpha;    } completion:^(BOOL finished) {        // 重要,動畫完成之后,每次都要記得 移除兩個view,下次開始拖動時,再添加進來        [_screenshotImgView removeFromSuperview];        [_coverView removeFromSuperview];    }];} else {    // 如果手指移動的距離還超過了屏幕的一半,往右邊挪    [UIView animateWithDuration:0.3 animations:^{        // 讓被右移的view完全挪到屏幕的最右邊,結(jié)束之后,還要記得清空view的transform        self.view.transform = CGAffineTransformMakeTranslation(width, 0);        // 讓imageView位移還原        _screenshotImgView.transform = CGAffineTransformMakeTranslation(0, 0);        // 讓遮蓋alpha變?yōu)?,變得完全透明        _coverView.alpha = 0;    } completion:^(BOOL finished) {        // 重要~~讓被右移的view完全挪到屏幕的最右邊,結(jié)束之后,還要記得清空view的transform,不然下次再次開始drag時會出問題,因為view的transform沒有歸零        self.view.transform = CGAffineTransformIdentity;        // 移除兩個view,下次開始拖動時,再加回來        [_screenshotImgView removeFromSuperview];        [_coverView removeFromSuperview];        // 執(zhí)行正常的Pop操作:移除棧頂控制器,讓真正的前一個控制器成為導(dǎo)航控制器的棧頂控制器        [self popViewControllerAnimated:NO];    }];}

}

實現(xiàn)截圖保存功能,并在Push前截圖
- (void)screenShot{// 將要被截圖的view,即窗口的根控制器的viewUIViewController *beyondVC = self.view.window.rootViewController;// 背景圖片 總的大小CGSize size = beyondVC.view.frame.size;// 開啟上下文,使用參數(shù)之后,截出來的是原圖(YES  0.0 質(zhì)量高)UIGraphicsBeginImageContextWithOptions(size, YES, 0.0);// 要裁剪的矩形范圍CGRect rect = CGRectMake(0, 0, ScreenWidth, ScreenHeight);//注:iOS7以后renderInContext:由drawViewHierarchyInRect:afterScreenUpdates:替代[beyondVC.view drawViewHierarchyInRect:rect  afterScreenUpdates:NO];// 從上下文中,取出UIImageUIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();// 添加截取好的圖片到圖片數(shù)組if (snapshot) {    [_screenshotImgs addObject:snapshot];}// 千萬記得,結(jié)束上下文(移除棧頂?shù)幕诋?dāng)前位圖的圖形上下文)UIGraphicsEndImageContext();}- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{  //有在導(dǎo)航控制器里面有子控制器的時候才需要截圖if (self.viewControllers.count >= 1) {    // 調(diào)用自定義方法,使用上下文截圖    [self screenShot];}// 截圖完畢之后,才調(diào)用父類的push方法[super pushViewController:viewController animated:YES];}
重寫常用的pop方法

在一開始基本原理地方,我們說過pop時要刪除最后一張截圖,用來保證數(shù)組中的最后一張截圖是上一個控制器,但是很多情況下我們可能調(diào)用的是導(dǎo)航欄的popToViewController: animated:方法或popToRootViewControllerAnimated:來返回,這種情況下,我們刪除的可能就不是一張截圖,因此我們需要分別重寫這些Pop方法,去確定我們要刪除多少張圖片,代碼如下

- (UIViewController *)popViewControllerAnimated:(BOOL)animated{   [_screenshotImgs removeLastObject];   return [super popViewControllerAnimated:animated];}- (NSArray<UIViewController *> *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated{for (NSInteger i = self.viewControllers.count - 1; i > 0; i--) {    if (viewController == self.viewControllers[i]) {        break;    }    [_screenshotImgs removeLastObject];}return [super popToViewController:viewController animated:animated];}- (NSArray<UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated{[_screenshotImgs removeAllObjects];return [super popToRootViewControllerAnimated:animated];}
※在指定的控制器屏蔽手勢

在上面代碼中,我們使用的是側(cè)滑手勢,并將相應(yīng)區(qū)域設(shè)置為屏幕左側(cè)。之所以不用全屏滑動,是因為全屏滑動手勢在有些時候會和其他手勢沖突,如果沖突的是我們自定義的手勢,自然好解決,但如果是系統(tǒng)手勢,如TableView的左滑菜單操作,這個事情就很蛋疼的。但是如果必須要做全屏滑動手勢的話,我們可以對代碼稍作修改,某些控制器中屏蔽手勢。

首先給導(dǎo)航欄添加禁用名單數(shù)組并配置

...@property(nonatomic,copy)NSArray * forbiddenArray;...- (void)viewDidLoad {[super viewDidLoad];//原來代碼...  //將手勢禁用,之后在Push時根據(jù)條件開啟 self.panGestureRec.enabled = enable//將需要禁用手勢的控制器的類名加到這個數(shù)組self.forbiddenArray = @[@"SCViewController",@"ManageAddressViewController"];}- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{//在指定控制器中禁用手勢  解決滑動返回手勢和某些手勢沖突問題BOOL enable = YES;for (NSString * string in self.forbiddenArray) {    NSString * className = NSStringFromClass([viewController class]);    if ([string isEqualToString:className]) {        enable = NO;    }}self.panGestureRec.enabled = enable;//原有代碼...}- (UIViewController *)popViewControllerAnimated:(BOOL)animated{NSInteger count = self.viewControllers.count;NSString * className = nil;if (count >= 2) {    className = NSStringFromClass([self.viewControllers[count -2] class]);}BOOL enable = YES;for (NSString * string in self.forbiddenArray) {    if ([string isEqualToString:className]) {        enable = NO;    }}self.panGestureRec.enabled = enable;//原有代碼...return [super popViewControllerAnimated:animated];}
發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 遵义市| 凌源市| 武陟县| 台前县| 南宁市| 肥乡县| 壶关县| 兴安盟| 永川市| 晋中市| 南涧| 隆尧县| 额尔古纳市| 新密市| 中宁县| 微博| 东阳市| 四平市| 白银市| 聂拉木县| 敦煌市| 白城市| 西华县| 搜索| 丰县| 百色市| 桃园县| 宁国市| 商城县| 富顺县| 西安市| 武隆县| 肥城市| 汝城县| 潜江市| 平舆县| 平舆县| 蓬莱市| 建阳市| 东阿县| 常宁市|