App開發流程之右滑返回手勢功能續

来源:http://www.cnblogs.com/ALongWay/archive/2016/09/22/5896982.html
-Advertisement-
Play Games

上一篇記錄了利用系統私有變數和方法實現右滑返回手勢功能:http://www.cnblogs.com/ALongWay/p/5893515.html 這篇繼續記錄另一種方案:利用UINavigationController的delegate方法。 核心代理方法有如下兩個: 第一個代理方法,要求在視圖 ...


上一篇記錄了利用系統私有變數和方法實現右滑返回手勢功能:http://www.cnblogs.com/ALongWay/p/5893515.html

這篇繼續記錄另一種方案:利用UINavigationController的delegate方法。

核心代理方法有如下兩個:

- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                          interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController NS_AVAILABLE_IOS(7_0);

- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                   animationControllerForOperation:(UINavigationControllerOperation)operation
                                                fromViewController:(UIViewController *)fromVC
                                                  toViewController:(UIViewController *)toVC  NS_AVAILABLE_IOS(7_0);

第一個代理方法,要求在視圖控制器變換時候,返回一個實現UIViewControllerInteractiveTransitioning協議的對象,該對象用於控制交互進度和狀態。

UIKit.h/UIViewControllerTransitioning.h中已經準備好了一個這樣的對象,名為UIPercentDrivenInteractiveTransition。

主要關註其如下三個方法:

- (void)updateInteractiveTransition:(CGFloat)percentComplete;//控制交互進度的百分比

- (void)cancelInteractiveTransition;//中途取消交互

- (void)finishInteractiveTransition;//完成交互

可以在一個實現了代理方法的類中,定義一個手勢目標方法來調用上述方法,達到控製作為變數的UIPercentDrivenInteractiveTransition對象的目的。

主要代碼如下:

- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
    if ([animationController isKindOfClass:[PopAnimation class]])
        return _interactivePopTransition;
    
    return nil;
}

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
    if (operation == UINavigationControllerOperationPop)
        return _popAnimation;
    
    return nil;
}

- (void)handlePopGestureOperation:(UIPanGestureRecognizer *)recognizer{
    //將手指在屏幕上的移動距離與屏幕寬度比例作為動畫的進度
    CGPoint translationInScreen = [recognizer translationInView:[UIApplication sharedApplication].keyWindow];
    CGFloat progress = translationInScreen.x / [UIScreen mainScreen].bounds.size.width;
    progress = MIN(1.0, MAX(0.0, progress));
    
    if (recognizer.state == UIGestureRecognizerStateBegan) {
        //新建手勢交互對象
        _interactivePopTransition = [[UIPercentDrivenInteractiveTransition alloc] init];

        //開始執行pop動畫
        [_naviController popViewControllerAnimated:YES];
    }else if (recognizer.state == UIGestureRecognizerStateChanged) {
         //更新進度
        [_interactivePopTransition updateInteractiveTransition:progress];
    }else if (recognizer.state == UIGestureRecognizerStateEnded
              || recognizer.state == UIGestureRecognizerStateCancelled) {
        //手勢結束時如果進度大於一半,那麼就完成pop操作,否則取消
        if (progress > 0.5) {
            [_interactivePopTransition finishInteractiveTransition];
        }else {
            [_interactivePopTransition cancelInteractiveTransition];
        }
        
        //手勢交互結束,清理對象
        _interactivePopTransition = nil;
    }
}

 

 

第二個代理方法,要求在操作開始時候,返回一個實現UIViewControllerAnimatedTransitioning協議的對象,該對象實現了動畫內容和執行時間。

該協議有三個方法:

// This is used for percent driven interactive transitions, as well as for container controllers that have companion animations that might need to
// synchronize with the main animation. 
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
// This method can only  be a nop if the transition is interactive and not a percentDriven interactive transition.
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;


@optional

// This is a convenience and if implemented will be invoked by the system when the transition context's completeTransition: method is invoked.
- (void)animationEnded:(BOOL) transitionCompleted;

 

第一個方法返回動畫時間;第二個方法,可以得到實現了UIViewControllerContextTransitioning協議的對象;第三個方法可選,如果transitionContext對象調用了completeTransition:方法,該代理方法會被調用。

重點是第二個方法的transitionContext對象,其重要的方法如下:

// The view in which the animated transition should take place.
- (nullable UIView *)containerView;//動畫所在的容器view
- (BOOL)transitionWasCancelled;//轉換是否取消// This must be called whenever a transition completes (or is cancelled.)
// Typically this is called by the object conforming to the
// UIViewControllerAnimatedTransitioning protocol that was vended by the transitioning
// delegate.  For purely interactive transitions it should be called by the
// interaction controller. This method effectively updates internal view
// controller state at the end of the transition.
- (void)completeTransition:(BOOL)didComplete;//完成轉換,必須在動畫完成時調用


// Currently only two keys are defined by the
// system - UITransitionContextToViewControllerKey, and
// UITransitionContextFromViewControllerKey. 
// Animators should not directly manipulate a view controller's views and should
// use viewForKey: to get views instead.
- (nullable __kindof UIViewController *)viewControllerForKey:(NSString *)key;//取得視圖控制器

// Currently only two keys are defined by the system -
// UITransitionContextFromViewKey, and UITransitionContextToViewKey
// viewForKey: may return nil which would indicate that the animator should not
// manipulate the associated view controller's view.
- (nullable __kindof UIView *)viewForKey:(NSString *)key NS_AVAILABLE_IOS(8_0);//取得視圖控制器的view,相容iOS7,可以直接用上面取到的viewController.view

 

創建一個實現UIViewControllerAnimatedTransitioning協議的對象PopAnimation,重要代碼如下:

- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
    return _transitionDuration;
}

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

    //當前控制器
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    //動畫結束顯示的控制器
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    //執行交互動畫的容器view
    UIView *containerView = [transitionContext containerView];
    [containerView insertSubview:toViewController.view belowSubview:fromViewController.view];
    
    NSTimeInterval duration = [self transitionDuration:transitionContext];
    
    switch (_popAnimationType) {
        case PopAnimationTranslation: {
            CGFloat containerWidth = containerView.frame.size.width;
            toViewController.view.transform = CGAffineTransformMakeTranslation(-containerWidth / 4.0, 0);

            [UIView animateWithDuration:duration animations:^{
                toViewController.view.transform = CGAffineTransformMakeTranslation(0, 0);
                fromViewController.view.transform = CGAffineTransformMakeTranslation(containerWidth, 0);
            }completion:^(BOOL finished) {
                //動畫結束,必須調用此方法
                [transitionContext completeTransition:!transitionContext.transitionWasCancelled];
            }];

            break;
        }
        case PopAnimationFlip: {
            [UIView beginAnimations:@"View Flip" context:nil];
            [UIView setAnimationDuration:duration];
            [UIView setAnimationDelegate:self];
            [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:containerView cache:YES];
            [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:)];
            [UIView commitAnimations];
            [containerView exchangeSubviewAtIndex:0 withSubviewAtIndex:1];

            break;
        }
        case PopAnimationCube: {
            CATransition *transiton = [CATransition animation];
            transiton.type = @"cube";
            transiton.subtype = @"fromLeft";
            transiton.duration = duration;
            transiton.removedOnCompletion = NO;
            transiton.fillMode = kCAFillModeForwards;
            transiton.delegate = self;
            [containerView.layer addAnimation:transiton forKey:nil];
            [containerView exchangeSubviewAtIndex:0 withSubviewAtIndex:1];

            break;
        }
    }
}

- (void)animationDidStop:(CATransition *)anim finished:(BOOL)flag {
    [_transitionContext completeTransition:!_transitionContext.transitionWasCancelled];
}

 

剩下的工作:

在上一篇的基礎上,修改UINavigationController的base_pushViewController方法

- (void)base_pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.base_panGestureRecognizer]) {
        [self.interactivePopGestureRecognizer.view addGestureRecognizer:self.base_panGestureRecognizer];
        
        self.base_panGestureRecognizer.delegate = [self base_panGestureRecognizerDelegateObject];
        
        self.base_currentDelegateObject = [[NavigationControllerDelegateObject alloc] initWithUINavigationController:self];
        
        [self.base_panGestureRecognizer addTarget:self.base_currentDelegateObject action:@selector(handlePopGestureOperation:)];
        
        self.interactivePopGestureRecognizer.enabled = NO;
    }
    
    [self base_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController];
    
    if (![self.viewControllers containsObject:viewController]) {
        [self base_pushViewController:viewController animated:animated];
    }
}

可以看到,去除了私有變數和方法的調用,自定義了名為NavigationControllerDelegateObject的對象,其實現了UINavigationControllerDelegate協議,並實現了pan手勢的處理方法。在此,我暴露了該屬性對象,考慮到以後可能會增加或者修改UINavigationControllerDelegate的實現協議,可以為該對象增加分類來達到目的;還有一個目的,便於修改popAnimation的動畫類型和執行時間。

 

總結:

兩篇記錄,從不同的方向,記錄了右滑返回手勢功能的實現。利用系統變數和方法,方便快捷;利用協議方法,高度自定義。

但是,利用協議方法,需要處理的方面比較多。雖然上述已經記錄了實現核心,示例代碼中也有詳細代碼,但是仍然還有很多方面未考慮。

目前,我發現的一些問題:

1.導航欄交互效果也需要自行實現,暫未實現原生效果

2.導航欄由隱藏到顯示的效果,有時會出現異常

3.如果帶有bottomBar,效果不理想

 

如果有好的解決辦法,希望告知,謝謝。

 

base項目已更新:[email protected]:ALongWay/base.git
項目增加了新的UINavigationController+PopOperation分類,但是未引用到項目中,需要的同學,可先刪除PopGesture分類的引用,再添加該分類的應用。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 使用volley進行網路請求:需先將volley包導入androidstudio中 File下的Project Structrue,點加號導包 volley網路請求步驟: 1. 創建請求隊列 RequestQueue queue = Volley.newRequestQueue(this); 2.創 ...
  • 本文結合之前的動態創建fragment來進行一個實踐,來實現用Fragment創建一個選項卡 本文地址:http://www.cnblogs.com/wuyudong/p/5898075.html ,轉載請註明源地址。 項目佈局 新建Fragment1.java~Fragment4.java,其中F ...
  • 大家好,好久沒有跟新了。其實也就昨天到今天的時間。 前言:實際上,GIF動圖文件中包含了一組圖片及其信息數組,這些信息數據記錄著這一組圖片中各張圖片的播放時長等信息,我們可以將圖片和這些信息或取出來,使用UIImageView的幀動畫技術進行動畫播放。 好了不多說了 開始上代碼吧: 首先自己找一個G ...
  • 隨著Android應用開發規模的擴大,客戶端業務邏輯也越來越複雜,已然不是簡單的數據展示了。如同後端開發遇到瓶頸時採用的組件拆分思想,客戶端也需要進行架構設計,拆分視圖和數據,解除模塊之間的耦合,提高模塊內部的聚合度。 開始之前先上一張內部分享時用的PPT圖: 以上是筆者在客戶端開發過程中面臨的問題 ...
  • 1、SpannableString、SpannableStringBuilder與String的關係 首先SpannableString、SpannableStringBuilder基本上與String差不多,也是用來存儲字元串,但它們倆的特殊就在於有一個SetSpan()函數,能給這些存儲的Str... ...
  • Android Weekly Issue #223 中文筆記, 本期內容包括: Offline時間戳處理; Accessibility的安全問題可能並不是個問題; 如何在單元測試和UI測試之間共用代碼; Android中的指紋認證; 編譯時間Kotlin vs Java; MVP結合RxJava,... ...
  • 前言: 單挑Android項目,最近即時通訊用到環信,集成sdk的時候 官方有一句 即:混淆規則。 自己沒寫過關於混淆打包的文章,在此補上。 下麵瞭解Android studio環境下 項目混淆打包的操作。 一、打包: 即 將Android項目生成.apk文件,讓用戶去安裝。 1、工具欄 Build ...
  • 官方地址: http://global.18wifibank.com/ github: https://github.com/yibawifi/wifisdk ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...