讓我們一次性解決導航欄的所有問題

etun0094 8年前發布 | 11K 次閱讀 iOS開發 移動開發

前言

今天我們來重點討論導航欄返回的問題,包括各種問題的解決方案。

系統默認導航欄的返回按鈕和返回方式

在默認情況下,導航欄返回按鈕長這個樣子

導航欄默認返回按鈕

導航欄右上角的返回按鈕,其文本默認為上一個ViewController的標題,如果上一個ViewController沒有標題,則為Back(中文環境下為“返回”)。

在默認情況下,導航欄返回的點擊交互和滑動交互如下

默認導航欄交互

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

自定義左上角的返回按鈕

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

替換左上角返回按鈕

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

  • (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]; }</pre>

    我們來看一眼效果

    替換返回按鈕

    調整按鈕位置

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

    //創建返回按鈕
    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];;
    //創建UIBarButtonSystemItemFixedSpace
    UIBarButtonItem  spaceItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
    //將寬度設為負值
    spaceItem.width = -15;
    //將兩個BarButtonItem都返回給NavigationItem
    self.navigationItem.leftBarButtonItems = @[spaceItem,leftBarBtn];</pre> 
    

    我們來看一眼效果

    調整返回按鈕位置

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

    這個方法同樣適用于調整導航欄右側的按鈕

    讓滑動返回手勢生效

    如果使用自定義的按鈕去替換系統默認返回按鈕,會出現滑動返回手勢失效的情況。解決方法也很簡單,只需要重新添加導航欄的 interactivePopGestureRecognizer 的 delegate 即可。

    首先為ViewContoller添加 UIGestureRecognizerDelegate 協議

    然后設置代理

    self.navigationController.interactivePopGestureRecognizer.delegate = self;

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

    全屏滑動返回

    這個一個很常見的需求,網上解決方案也很多,這里將本人常用的方法貼到這里。僅供參考

    實現全屏滑動返回僅需在導航欄給導航欄添加 UIGestureRecognizerDelegate 協議,并在ViewDidLoad中寫入如下代碼

    // 獲取系統自帶滑動手勢的target對象
    id target = self.interactivePopGestureRecognizer.delegate;

// 創建全屏滑動手勢,調用系統自帶滑動手勢的target的action方法 UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:@selector(handleNavigationTransition:)];

// 設置手勢代理,攔截手勢觸發 pan.delegate = self;

// 給導航控制器的view添加全屏滑動手勢 [self.view addGestureRecognizer:pan];

// 禁止使用系統自帶的滑動手勢 self.interactivePopGestureRecognizer.enabled = NO;</pre>

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

全屏滑動返回.gif

成功

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

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

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

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

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

整體滑動返回

這種解決方案等于將兩個NavigationBar獨立開來,因此可以相對完美的解決導航欄滑動切換中的種種Bug。

接下來,我們來看看如何實現這種效果。

基本原理

以我個人的認知,實現這個效果有三種基本思路:

  1. 使用 UINavigationController 自帶的 setNavigationBarHidden: animated: 方法來實現,每次push或pop時,在當前控制器的 viewWillDisappear: 中設置隱藏,在要跳轉的控制器的 viewWillAppear: 中設置導航欄顯示。
  2. 在每次Push前對當前頁面進行截圖并保存到數組,Pop時取數組最后一個元素顯示,滑動結束后調用系統Pop方法并刪除最后一張截圖。
  3. 使用iOS 7之后開放的,UIViewControllerAnimatedTransitioning協議,來實現自定義導航欄轉場動畫及交互。

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

在iOS的交互中,push一般通過按鈕的點擊事件或View的 tap 事件觸發,而pop則可能通過事件觸發,也可能通過右滑手勢觸發。因此,我們將這個我們要實現的動畫效果分為交互效果和無交互效果兩種,分別實現這兩種效果之后就能較為完美的解決Push和Pop的動畫問題。

實現交互動畫效果

準備需要使用的數組及手勢

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,創建Pan手勢識別器,并綁定監聽方法 _panGestureRec = [[UIScreenEdgePanGestureRecognizer alloc]initWithTarget:self action:@selector(panGestureRec:)]; _panGestureRec.edges = UIRectEdgeLeft; // 為導航控制器的view添加Pan手勢識別器 [self.view addGestureRecognizer:_panGestureRec];

// 2.創建截圖的ImageView _screenshotImgView = [[UIImageView alloc] init]; // app的frame是包括了狀態欄高度的frame _screenshotImgView.frame = CGRectMake(0, 0, ScreenWidth, ScreenHeight);

// 3.創建截圖上面的黑色半透明遮罩 _coverView = [[UIView alloc] init]; // 遮罩的frame就是截圖的frame _coverView.frame = _screenshotImgView.frame; // 遮罩為黑色 _coverView.backgroundColor = [UIColor blackColor];

// 4.存放所有的截圖數組初始化 _screenshotImgs = [NSMutableArray array]; }</pre>

實現手勢的相應事件

// 響應手勢的方法

  • (void)panGestureRec:(UIPanGestureRecognizer *)panGestureRec {

// 如果當前顯示的控制器已經是根控制器了,不需要做任何切換動畫,直接返回 if(self.visibleViewController == self.viewControllers[0]) return; // 判斷pan手勢的各個階段 switch (panGestureRec.state) { case UIGestureRecognizerStateBegan: // 開始拖拽階段 [self dragBegin]; break;

case UIGestureRecognizerStateEnded:
    // 結束拖拽階段
    [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顯示截圖數組中的最后(最新)一張截圖 _screenshotImgView.image = [_screenshotImgs lastObject]; //_screenshotImgView.transform = CGAffineTransformMakeTranslation(ScreenWidth, 0); }

// 默認的將要變透明的遮罩的初始透明度(全黑)

define kDefaultAlpha 0.6

// 當拖動的距離,占了屏幕的總寬高的3/4時, 就讓imageview完全顯示,遮蓋完全消失

define kTargetTranslateScale 0.75

pragma mark 正在拖動,動畫效果的精髓,進行位移和透明度變化

  • (void)dragging:(UIPanGestureRecognizer *)pan {

// 得到手指拖動的位移 CGFloat offsetX = [pan translationInView:self.view].x;

// 讓整個view都平移 // 挪動整個導航view if (offsetX > 0) { self.view.transform = CGAffineTransformMakeTranslation(offsetX, 0); }

// 計算目前手指拖動位移占屏幕總的寬高的比例,當這個比例達到3/4時, 就讓imageview完全顯示,遮蓋完全消失 double currentTranslateScaleX = offsetX/self.view.frame.size.width;

if (offsetX < ScreenWidth) {

_screenshotImgView.transform = CGAffineTransformMakeTranslation((offsetX - ScreenWidth) * 0.6, 0);

}

// 讓遮蓋透明度改變,直到減為0,讓遮罩完全透明,默認的比例-(當前平衡比例/目標平衡比例)默認的比例 double alpha = kDefaultAlpha - (currentTranslateScaleX/kTargetTranslateScale) kDefaultAlpha; _coverView.alpha = alpha; }

pragma mark 結束拖動,判斷結束時拖動的距離作相應的處理,并將圖片和遮罩從父控件上移除

  • (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大小恢復默認的translation _screenshotImgView.transform = CGAffineTransformMakeTranslation(-ScreenWidth, 0); // 讓遮蓋的透明度恢復默認的alpha 1.0 _coverView.alpha = kDefaultAlpha; } completion:^(BOOL finished) { // 重要,動畫完成之后,每次都要記得 移除兩個view,下次開始拖動時,再添加進來 [_screenshotImgView removeFromSuperview]; [_coverView removeFromSuperview]; }]; } else { // 如果手指移動的距離還超過了屏幕的一半,往右邊挪 [UIView animateWithDuration:0.3 animations:^{ // 讓被右移的view完全挪到屏幕的最右邊,結束之后,還要記得清空view的transform self.view.transform = CGAffineTransformMakeTranslation(width, 0); // 讓imageView位移還原 _screenshotImgView.transform = CGAffineTransformMakeTranslation(0, 0); // 讓遮蓋alpha變為0,變得完全透明 _coverView.alpha = 0; } completion:^(BOOL finished) { // 重要讓被右移的view完全挪到屏幕的最右邊,結束之后,還要記得清空view的transform,不然下次再次開始drag時會出問題,因為view的transform沒有歸零 self.view.transform = CGAffineTransformIdentity; // 移除兩個view,下次開始拖動時,再加回來 [_screenshotImgView removeFromSuperview]; [_coverView removeFromSuperview];

    // 執行正常的Pop操作:移除棧頂控制器,讓真正的前一個控制器成為導航控制器的棧頂控制器
    [self popViewControllerAnimated:NO];
}];

}</pre>

}

實現截圖保存功能,并在Push前截圖

  • (void)screenShot { // 將要被截圖的view,即窗口的根控制器的view UIViewController beyondVC = self.view.window.rootViewController; // 背景圖片 總的大小 CGSize size = beyondVC.view.frame.size; // 開啟上下文,使用參數之后,截出來的是原圖(YES 0.0 質量高) UIGraphicsBeginImageContextWithOptions(size, YES, 0.0); // 要裁剪的矩形范圍 CGRect rect = CGRectMake(0, 0, ScreenWidth, ScreenHeight); //注:iOS7以后renderInContext:由drawViewHierarchyInRect:afterScreenUpdates:替代 [beyondVC.view drawViewHierarchyInRect:rect afterScreenUpdates:NO]; // 從上下文中,取出UIImage UIImage snapshot = UIGraphicsGetImageFromCurrentImageContext(); // 添加截取好的圖片到圖片數組 if (snapshot) { [_screenshotImgs addObject:snapshot]; } // 千萬記得,結束上下文(移除棧頂的基于當前位圖的圖形上下文) UIGraphicsEndImageContext(); }
  • (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated { //有在導航控制器里面有子控制器的時候才需要截圖 if (self.viewControllers.count >= 1) { // 調用自定義方法,使用上下文截圖 [self screenShot]; } // 截圖完畢之后,才調用父類的push方法 [super pushViewController:viewController animated:YES]; }</pre>

    重寫常用的pop方法

    在一開始基本原理地方,我們說過pop時要刪除最后一張截圖,用來保證數組中的最后一張截圖是上一個控制器,但是很多情況下我們可能調用的是導航欄的 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]; }</pre>

    ※在指定的控制器屏蔽手勢

    在上面代碼中,我們使用的是側滑手勢,并將相應區域設置為屏幕左側。

    之所以不用全屏滑動,是因為全屏滑動手勢在有些時候會和其他手勢沖突,如果沖突的是我們自定義的手勢,自然好解決,但如果是系統手勢,如TableView的左滑菜單操作,這個事情就很蛋疼的。

    但是如果必須要做全屏滑動手勢的話,我們可以對代碼稍作修改,某些控制器中屏蔽手勢。

    首先給導航欄添加禁用名單數組并配置

    ...
    @property(nonatomic,copy)NSArray * forbiddenArray;
    ...
  • (void)viewDidLoad { [super viewDidLoad]; //原來代碼 ... //將手勢禁用,之后在Push時根據條件開啟 self.panGestureRec.enabled = enable //將需要禁用手勢的控制器的類名加到這個數組 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]; }</pre>

到了這里,我們已經完成了交互式的切換動畫,效果跟開頭一樣,就不再截圖。接下來我們來解決另一個大Boss-非交互式動畫

實現交互動畫效果

理論基礎

這里我們就要用到之前說的 UIViewControllerAnimatedTransitioning 來實現。

實現原理

注:FromVC代表即將消失的視圖控制器,ToVC表示將要展示的視圖控制器

我們要實現的效果:

Push的時候,FromVC往左移動,ToVC從屏幕右側出現跟隨FromVC左移直至FromVC消失,此時ToVC剛好完整顯示在屏幕上。

Pop的時候,FromVC向右移動,ToVC從屏幕邊緣出現跟隨FromVC向右移動直至FromVC消失,此時ToVC剛好完整顯示在屏幕上

實現的時候,我們依然需要將Push和Pop分開討論

先說Pop

1.和交互式動畫一樣,每次Push時對屏幕截屏并保存,Pop的再次截屏但不保存

2.把Pop時截取的圖片作為FromVC展示,把Push到這個界面時截取的圖片作為ToVC展示

3.并對兩張圖片做位移動畫,動畫結束后移除兩張圖片

然后是Push

1.Push時先對當前屏幕截屏。

2.將截取的圖片保存方便Pop回來時使用,并把這張圖片作為這次Push的FromVC保存。

3.獲取當前導航欄控制器對象,調整其Transform屬性中的位移參數作為ToVC展示

4.對截圖和導航欄做位移,動畫結束后直接移除截屏圖片

為什么要對導航欄作位移?

首先,在Push結束之前,我們是無法知道ToVC具體是什么樣子,系統的截屏方法對于未加載出來的View是無能為力的,而UIView的 snapshotViewAfterScreenUpdates: 方法又無法帶著導航欄一起映射到一個新的View上,因此視覺效果很差。

正好在Pop的時候,為了達到想要的動畫效果,用來展示的兩張圖片都需要放到導航欄的View上,因此在Push的時候我們就直接將導航欄的View做一個放射變換,當然,這也就意味著,當我們Push的時候,截屏就不能再放到導航欄上,而是應該放到它的“更上一層“ -- UITabbarController 的View上

讓我們擼一發代碼

根據上述實現原理,我們可以知道,我們的主要工作重點在于打造一個合適的動畫控制器。更準確的說,我們需要實現的細節都在 UIViewControllerAnimatedTransitioning 中,由于之前解釋的很詳細,這里我直接貼上相應代碼供參考

-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{

UIImageView screentImgView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, ScreenWidth, ScreenHeight)]; UIImage screenImg = [self screenShot]; screentImgView.image =screenImg;

//取出fromViewController,fromView和toViewController,toView UIViewController fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; // UIView fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; UIViewController toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView toView = [transitionContext viewForKey:UITransitionContextToViewKey];

CGRect fromViewEndFrame = [transitionContext finalFrameForViewController:fromViewController]; fromViewEndFrame.origin.x = ScreenWidth; CGRect fromViewStartFrame = fromViewEndFrame; CGRect toViewEndFrame = [transitionContext finalFrameForViewController:toViewController]; CGRect toViewStartFrame = toViewEndFrame;

UIView * containerView = [transitionContext containerView];

if (self.navigationOperation == UINavigationControllerOperationPush) {

[self.screenShotArray addObject:screenImg];
//toViewStartFrame.origin.x += ScreenWidth;
[containerView addSubview:toView];

toView.frame = toViewStartFrame;

UIView * nextVC = [[UIView alloc]initWithFrame:CGRectMake(ScreenWidth, 0, ScreenWidth, ScreenHeight)];
 //[nextVC addSubview:[toView snapshotViewAfterScreenUpdates:YES]];

[self.navigationController.tabBarController.view insertSubview:screentImgView atIndex:0];

//[self.navigationController.tabBarController.view addSubview:nextVC];
nextVC.layer.shadowColor = [UIColor blackColor].CGColor;
nextVC.layer.shadowOffset = CGSizeMake(-0.8, 0);
nextVC.layer.shadowOpacity = 0.6;

self.navigationController.view.transform = CGAffineTransformMakeTranslation(ScreenWidth, 0);

[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
    //toView.frame = toViewEndFrame;
self.navigationController.view.transform = CGAffineTransformMakeTranslation(0, 0);
    screentImgView.center = CGPointMake(-ScreenWidth/2, ScreenHeight / 2);
    //nextVC.center = CGPointMake(ScreenWidth/2, ScreenHeight / 2);


} completion:^(BOOL finished) {

    [nextVC removeFromSuperview];
    [screentImgView removeFromSuperview];
    [transitionContext completeTransition:YES];
}];

} if (self.navigationOperation == UINavigationControllerOperationPop) {

fromViewStartFrame.origin.x = 0;
[containerView addSubview:toView];

UIImageView * lastVcImgView = [[UIImageView alloc]initWithFrame:CGRectMake(-ScreenWidth, 0, ScreenWidth, ScreenHeight)];
lastVcImgView.image = [self.screenShotArray lastObject];
screentImgView.layer.shadowColor = [UIColor blackColor].CGColor;
screentImgView.layer.shadowOffset = CGSizeMake(-0.8, 0);
screentImgView.layer.shadowOpacity = 0.6;
[self.navigationController.tabBarController.view addSubview:lastVcImgView];
[self.navigationController.tabBarController.view addSubview:screentImgView];

// fromView.frame = fromViewStartFrame; [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{

    screentImgView.center = CGPointMake(ScreenWidth * 3 / 2 , ScreenHeight / 2);
    lastVcImgView.center = CGPointMake(ScreenWidth/2, ScreenHeight/2);
    //fromView.frame = fromViewEndFrame;

} completion:^(BOOL finished) {
    //[self.navigationController setNavigationBarHidden:NO];
    [lastVcImgView removeFromSuperview];
    [screentImgView removeFromSuperview];
    [self.screenShotArray removeLastObject];
    [transitionContext completeTransition:YES];

}];

}

}

  • (void)removeLastScreenShot { [self.screenShotArray removeLastObject]; }
  • (UIImage )screenShot { // 將要被截圖的view,即窗口的根控制器的view(必須不含狀態欄,默認ios7中控制器是包含了狀態欄的) UIViewController beyondVC = self.navigationController.view.window.rootViewController; // 背景圖片 總的大小 CGSize size = beyondVC.view.frame.size; // 開啟上下文,使用參數之后,截出來的是原圖(YES 0.0 質量高) UIGraphicsBeginImageContextWithOptions(size, YES, 0.0); // 要裁剪的矩形范圍 CGRect rect = CGRectMake(0, 0, ScreenWidth, ScreenHeight); //注:iOS7以后renderInContext:由drawViewHierarchyInRect:afterScreenUpdates:替代 [beyondVC.view drawViewHierarchyInRect:rect afterScreenUpdates:NO]; // 從上下文中,取出UIImage UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();

// 千萬記得,結束上下文(移除棧頂的基于當前位圖的圖形上下文) UIGraphicsEndImageContext();

// 返回截取好的圖片 return snapshot;

}</pre>

注: removeLastScreenShot 需要在使用滑動手勢Pop后調用,用來清除動畫控制器中保存的截圖,否則當交互式和非交互式動畫交替使用時,會出現截圖混亂的問題。

看看效果

我們將動畫持續時間調制兩秒,觀察一下效果

完成效果.gif

使用方法:

1.將這四個文件導入工程

2.將需要動畫的導航欄繼承KLTNavigationController即可

 

 

來自:http://www.jianshu.com/p/31f177158c9e

 

 本文由用戶 etun0094 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!