題外話:最近一直很閑野瘦,項(xiàng)目上基本沒有啥大的需求描沟。對于程序員來說,如果沒有需求其實(shí)是一件很難受的事情鞭光,之前好多次在項(xiàng)目中沒事找事吏廉,該優(yōu)化的優(yōu)化,該整理的整理惰许∠玻可能好多程序員都遇到過與我類似的情況。但是程序員真的需要通過刷項(xiàng)目去提高自己嗎啡省?程序員的功力體現(xiàn)在處理項(xiàng)目的細(xì)節(jié)上娜睛,有可能為了改進(jìn)一點(diǎn)點(diǎn)的體驗(yàn)就要付出很大的代價(jià),就要接觸更多的知識點(diǎn)卦睹。做的項(xiàng)目多,但是不精那么只能代表自己在比較淺顯的領(lǐng)域比較熟練而已方库。以上觀點(diǎn)是我在最近思考得來的结序,并不一定正確,甚至明天一早我就會推翻這種想法纵潦,請大家謹(jǐn)慎參考??徐鹤。
一、什么是隔離導(dǎo)航控制器邀层?
這個(gè)名詞純粹是我自己瞎編的返敬,我不知道有沒有這種說法,至少我沒有看到寥院。我所說的隔離導(dǎo)航控制器是指:在導(dǎo)航欄樣式不同的頁面采用不同的導(dǎo)航控制器【⒃現(xiàn)在同一個(gè)導(dǎo)航欄發(fā)生變化主要體現(xiàn)在:隱藏導(dǎo)航欄頁面跳轉(zhuǎn)到非隱藏導(dǎo)航欄的頁面、A顏色導(dǎo)航欄的頁面跳轉(zhuǎn)到B顏色導(dǎo)航欄的頁面秸谢、可跟隨滑動做動畫的頁面跳轉(zhuǎn)到固定導(dǎo)航欄的頁面凛澎。隔離導(dǎo)航控制器就是:當(dāng)頁面跳轉(zhuǎn)到導(dǎo)航欄不同的頁面時(shí)不再使用同一個(gè)導(dǎo)航控制器,而是彈出一個(gè)新的導(dǎo)航控制器估蹄,這個(gè)導(dǎo)航控制器的導(dǎo)航欄固定不變塑煎,如果發(fā)生變化那么再彈出一個(gè)新的導(dǎo)航控制器。
二臭蚁、為什么要隔離導(dǎo)航控制器最铁?
很簡單讯赏,因?yàn)槲乙呀?jīng)受夠了頁面的來回跳轉(zhuǎn)導(dǎo)致狀態(tài)欄儲存、變化冷尉、恢復(fù)這樣的過程待逞。尤其在側(cè)滑或者全屏滑動返回時(shí),導(dǎo)航欄的變化會有不好的體驗(yàn)网严。所以在很久以前我就在醞釀识樱,如果present出來一個(gè)導(dǎo)航控制器就好了(隔離導(dǎo)航控制器并不是一個(gè)很好地解決辦法,但是可以試一試震束,畢竟我很閑)怜庸。因此我在項(xiàng)目中試了一下,結(jié)果遇到了一些問題垢村,自己挖的坑割疾,跪著也要填滿了。
三嘉栓、實(shí)現(xiàn)右進(jìn)右出的present
ViewController 的present樣式一共有4種:
typedef NS_ENUM(NSInteger, UIModalTransitionStyle) {
UIModalTransitionStyleCoverVertical = 0,//默認(rèn)
UIModalTransitionStyleFlipHorizontal __TVOS_PROHIBITED,//翻轉(zhuǎn)
UIModalTransitionStyleCrossDissolve,//透明度漸變
UIModalTransitionStylePartialCurl NS_ENUM_AVAILABLE_IOS(3_2) __TVOS_PROHIBITED,//翻書
};
[vc setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal]; //present 前加上這句就行
[self presentViewController:vc animated:YES completion:nil];
沒有我們需要的效果宏榕,因此我們需要利用iOS 7 以后的轉(zhuǎn)場動畫來實(shí)現(xiàn)。說到轉(zhuǎn)場動畫不怕大家笑話侵佃,我已經(jīng)看了好多遍了麻昼,但是依然記不住那些名字。每次要用都要去大神的博客里再學(xué)習(xí)一邊馋辈。我的代碼也是使用大神的代碼抚芦,因?yàn)橛X得沒有必要再寫一遍。只不過我做了略微的修改迈螟。
// NormalDismissAnimation
// 4. Do animate now
NSTimeInterval duration = [self transitionDuration:transitionContext];
toVC.view.transform = CGAffineTransformMakeTranslation(-100, 0);//主要是為了上下兩個(gè)控制器有聯(lián)動的效果
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
fromVC.view.frame = finalFrame;
toVC.view.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
//BouncePresentAnimation
// 4. Do animate now
NSTimeInterval duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
toVC.view.frame = finalFrame;
fromVC.view.transform = CGAffineTransformMakeTranslation(-100, 0);//主要是為了有上下兩個(gè)控制器聯(lián)動的效果
} completion:^(BOOL finished) {
fromVC.view.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:YES];
}];
//SwipeUpInteractiveTransition
- (void)handleGesture:(UIPanGestureRecognizer *)gestureRecognizer {
CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view.superview];
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan:
// 1. Mark the interacting flag. Used when supplying it in delegate.
self.interacting = YES;
[self.presentingVC dismissViewControllerAnimated:YES completion:nil];
break;
case UIGestureRecognizerStateChanged: {
// 2. Calculate the percentage of guesture
CGSize screenSize = [UIScreen mainScreen].bounds.size;
CGFloat fraction = translation.x / screenSize.width;
//Limit it between 0 and 1
fraction = fminf(fmaxf(fraction, 0.0), 1.0);
self.shouldComplete = (fraction > 0.4);
[self updateInteractiveTransition:fraction];
break;
}
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
// 3. Gesture over. Check if the transition should happen or not
self.interacting = NO;
if (!self.shouldComplete || gestureRecognizer.state == UIGestureRecognizerStateCancelled) {
[self cancelInteractiveTransition];
} else {
[self finishInteractiveTransition];
}
break;
}
default:
break;
}
}
以上部分很簡單而且并不是我們所關(guān)心的部分(雖然名字有點(diǎn)難記憶叉抡,但是不難理解)。完成以上代碼答毫,就可以實(shí)現(xiàn)滑動返回+右進(jìn)右出的present樣式了褥民。
//還有一點(diǎn)需要注意的是,需要禁止全屏手勢在導(dǎo)航控制器棧里有多個(gè)元素時(shí)響應(yīng)
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
if ([self.presentingVC isKindOfClass:[BaseNavigationController class]]) {
BaseNavigationController *nav = (BaseNavigationController*)self.presentingVC;
if (nav.viewControllers.count >=2) {
return NO;
}
}
return YES;
}
四洗搂、遇到的問題
我們(或者說大多數(shù)APP)都有這樣的需求消返,當(dāng)收到推送時(shí)需要從底部彈出一個(gè)ViewController。 但是因?yàn)槲覀兪莗resent 出來了一些頁面蚕脏,當(dāng)收到推送的時(shí)候我們并不能一下子知道從哪個(gè)ViewController 上present 出來一個(gè)需要用戶響應(yīng)的控制器侦副。比方說 HomeViewController ---(右進(jìn)右出present)---->ReportMessageViewController后,從代碼的角度我們不能一下子拿到展現(xiàn)在用戶面前的控制器(因?yàn)槟悴恢朗欠褚呀?jīng)present了驼鞭,也不知道誰調(diào)用的present)秦驯。因此我遇到了第一個(gè)問題(很多的項(xiàng)目可以通過tab、nav 來確定挣棕,而且present 出控制器的場景比較固定):
1译隘、到底誰是當(dāng)前正在響應(yīng)的控制器亲桥?
要想解決這個(gè)問題我們首先要知道響應(yīng)者鏈條。即當(dāng)我們點(diǎn)擊了屏幕上的一個(gè)按鈕固耘,事件是怎么傳遞的题篷。UIView 和 UIViewController 都是繼承自UIResponder的,他們都可以成為響應(yīng)者厅目。因此我們只要遍歷屏幕上最上方的那些View(葉子節(jié)點(diǎn))番枚,縱向循環(huán)找到他們的nextResponder,直至找到為UIViewController類的響應(yīng)者损敷。
// 獲取某個(gè)view 的葉子 View(一般為Window)
/*
*/
+(NSMutableArray *)getTopSubViewsWithParentView:(UIView *)rootView{
NSMutableArray *stack = [NSMutableArray array];
NSMutableArray *leafNodes = [NSMutableArray array];//存放葉子節(jié)點(diǎn)
if (rootView.subviews.count == 0) {
return nil;
}
[stack addObjectsFromArray:rootView.subviews];//把根視圖的所有第一層子視圖入棧
while (stack.count != 0) {
UIView *subView = [stack lastObject];//取出頂部元素并判斷是否為葉子節(jié)點(diǎn)
[stack removeLastObject];
if (subView.subviews.count != 0) {//不是葉子節(jié)點(diǎn)的話將其子視圖繼續(xù)入棧
[stack addObjectsFromArray:subView.subviews];
}else{
[leafNodes addObject:subView];//如果是葉子節(jié)點(diǎn)則將其入棧(葉子節(jié)點(diǎn)的棧)
}
}
return leafNodes;
}
//獲取某個(gè)視圖在哪個(gè)控制器上
+(UIViewController*)getViewControllerWithView:(UIView*)view{
UIResponder *res = view;
while (res) {
if (res.nextResponder) {
res = res.nextResponder;
}
if ([res isKindOfClass:[UIViewController class]]) {
UIViewController *vc = (UIViewController*)res;
return vc;
}
}
return nil;
}
//這段代碼的意思是葫笼,如果我能判斷的更精確就精確些。比如某個(gè)導(dǎo)航控制器拗馒,你說他在響應(yīng)也行路星,他的top元素在響應(yīng)也行,顯然我想精確到top元素
+(UIViewController*)getCurrentVC{
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
NSMutableArray *array = [self getTopSubViewsWithParentView:keyWindow];
UINavigationController *nav = nil;
UITabBarController *tab = nil;
for (UIView *subView in array) {
UIViewController *vc = [self getViewControllerWithView:subView];
if (!([vc isKindOfClass:[UINavigationController class]] || [vc isKindOfClass:[UITabBarController class]])) {
return vc;
}
if ([vc isKindOfClass:[UINavigationController class]]) {
nav = (UINavigationController*)vc;
}
if ([vc isKindOfClass:[UITabBarController class]]) {
tab = (UITabBarController *)vc;
}
}
if (nav) {
return nav;
}
if (tab) {
return tab;
}
return nil;
}
有了這些代碼诱桂,問題一就解決了洋丐。
2、全屏滑動遇到了可以左右滑動的ScrollView怎么辦?
首先說明一下,這個(gè)問題我解決的并不完美项乒,因?yàn)槿コ薭ounces 效果。因?yàn)榛瑒覵crollView時(shí)九榔,ScrollView 的pan 手勢會優(yōu)先響應(yīng),并阻止其他手勢響應(yīng)涡相。首先我們先看一下手勢代理,看看都有哪些方法:
1 @protocol UIGestureRecognizerDelegate <NSObject>
2 @optional
3 // called when a gesture recognizer attempts to transition out of UIGestureRecognizerStatePossible. returning NO causes it to transition to UIGestureRecognizerStateFailed
4 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
5
6 // called when the recognition of one of gestureRecognizer or otherGestureRecognizer would be blocked by the other
7 // return YES to allow both to recognize simultaneously. the default implementation returns NO (by default no two gestures can be recognized simultaneously)
8 //
9 // note: returning YES is guaranteed to allow simultaneous recognition. returning NO is not guaranteed to prevent simultaneous recognition, as the other gesture's delegate may return YES
10 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;//手勢1剩蟀、手勢2 是否可以共存催蝗,即兩者都響應(yīng),收到事件繼續(xù)傳遞下去
11
12 // called once per attempt to recognize, so failure requirements can be determined lazily and may be set up between recognizers across view hierarchies
13 // return YES to set up a dynamic failure requirement between gestureRecognizer and otherGestureRecognizer
14 //
15 // note: returning YES is guaranteed to set up the failure requirement. returning NO does not guarantee that there will not be a failure requirement as the other gesture's counterpart delegate or subclass methods may return YES
16 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);//在other 響應(yīng)的情況下育特,自己是否不響應(yīng)
17 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
18
19 // called before touchesBegan:withEvent: is called on the gesture recognizer for a new touch. return NO to prevent the gesture recognizer from seeing this touch
20 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
21
22 // called before pressesBegan:withEvent: is called on the gesture recognizer for a new press. return NO to prevent the gesture recognizer from seeing this press
23 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePress:(UIPress *)press;
通過打印scrollView 的pan手勢我們會發(fā)現(xiàn) pan手勢的代理是scrollView 丙号,我嘗試過改變pan 的delegate 但是發(fā)現(xiàn)會崩潰,apple 是不允許改變這個(gè)delegate 的缰冤。所以我想到了寫一個(gè)UIScrollView的子類犬缨。
//
// PanScrollView.m
// Property
//
// Created by 高雅馨on 16/7/25.
// Copyright ? 2016年 高雅??. All rights reserved.
//
#import "PanScrollView.h"
@implementation PanScrollView
-(instancetype)init{
if (self = [super init]) {
self.bounces = NO;
}
return self;
}
/*
是否將相應(yīng)傳遞給other
當(dāng)偏移量X值為0的時(shí)候全屏手勢和pan 手勢同時(shí)響應(yīng)。全屏手勢向右滑動時(shí) 棉浸,由于bounces效果已經(jīng)被去掉了怀薛,所以偏移量不變,ViewController 只有dismiss 效果迷郑。當(dāng)向左滑動時(shí)全屏手勢沒有任何效果枝恋,只要scroll效果创倔。
*/
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
if (gestureRecognizer == self.panGestureRecognizer && otherGestureRecognizer == self.otherGes){
if (self.contentOffset.x==0) {
return YES;
}
}
return NO;
}
@end
至此,大部分可見的問題解決了焚碌,可能并不完美畦攘,也不適合,但是嘗試也是一種美好的回憶十电,希望能對大家有幫助知押。