2015-08-28 28 views
12

我想重新創建iOS 7/8日曆應用中顯示的搜索UI。以模態方式呈現搜索用戶界面不是問題。我使用UISearchController並以模式呈現它,就像UICatalog示例代碼顯示的那樣,它給了我一個很好的下拉動畫。嘗試從結果視圖控制器中推送視圖控制器時會出現問題。它沒有包裹在導航控制器中,所以我無法推送到它。如果我將它包裝在導航控制器中,那麼當我呈現UISearchController時,我不會獲得默認的下拉動畫。有任何想法嗎?如何在將視圖控制器以模態方式呈現時從搜索結果中推送到導航堆棧上?

編輯: 我得到它推包裹我的結果視圖控制器在導航控制器。但是,將新VC推入堆棧後,搜索欄仍然存在。

編輯(2): Apple的DTS表示,日曆應用程序使用非標準方法從搜索結果中推送。相反,他們建議從搜索控制器中刪除焦點,然後將焦點放在彈出窗口上。這與我設想的設置應用程序中的搜索方式類似。

回答

10

蘋果在那裏變得非常聰明,但它不是推動,即使它看起來像一個。

他們正在使用導航控制器中嵌入的視圖控制器中的自定義轉換(類似於導航控制器將執行的操作)。

您可以通過緩慢地邊緣滑動該細節視圖並讓前一個視圖開始出現來發現差異。注意頂部導航如何與細節一起向右滑動,而不是它的酒吧按鈕和標題過渡到位?

更新:

,你所看到的問題是,搜索控制器呈現上述的導航控制器。正如您發現的那樣,即使您將視圖控制器推到導航控制器的堆棧上,導航欄仍然位於搜索控制器的演示文稿下方,因此搜索欄會遮擋任何(推送的視圖控制器)導航欄。

如果您想在不關閉搜索控制器的情況下顯示搜索結果,則需要顯示自己的模態導航視圖控制器。

不幸的是,沒有任何轉換樣式可以讓您以與內置推動動畫相同的方式呈現您的導航控制器。

正如我所見,有三種效果需要重複。

  1. 隨着出現的視圖出現,底層內容變暗。
  2. 呈現的視圖有陰影。
  3. 底層內容的導航完全在屏幕外動畫,但其內容部分動畫。

I've reproduced the general effect within an interactive custom modal transition.它通常模仿日曆的動畫,但有一些區別(未顯示),例如鍵盤(重新)出現得太快。

提出的模態控制器是一個導航控制器。我連接了一個後退按鈕和邊緣滑動手勢(交互式)解僱它。

enter image description here

以下是所涉及的步驟:

  1. 在你的故事板,你會從顯示詳細信息的Segue公司類型更改爲當前模態

您可以留下介紹過渡設置爲默認,因爲他們會需要在代碼中被覆蓋。

  1. 在Xcode中,爲您的項目添加一個新的NavigationControllerDelegate文件。

    NavigationControllerDelegate.h:

    @interface NavigationControllerDelegate : NSObject <UINavigationControllerDelegate> 
    

    NavigationControllerDelegate.m:

    @interface NavigationControllerDelegate() <UIViewControllerTransitioningDelegate> 
    @property (nonatomic, weak) IBOutlet UINavigationController *navigationController; 
    @property (nonatomic, strong) UIPercentDrivenInteractiveTransition* interactionController; 
    @end 
    
    - (void)awakeFromNib 
    { 
        UIScreenEdgePanGestureRecognizer *panGestureRecognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]; 
        panGestureRecognizer.edges = UIRectEdgeLeft; 
    
        [self.navigationController.view addGestureRecognizer:panGestureRecognizer]; 
    } 
    
    #pragma mark - Actions 
    
    - (void)handlePan:(UIScreenEdgePanGestureRecognizer *)gestureRecognizer 
    { 
        UIView *view = self.navigationController.view; 
    
        if (gestureRecognizer.state == UIGestureRecognizerStateBegan) 
        { 
         if (!self.interactionController) 
         { 
          self.interactionController = [UIPercentDrivenInteractiveTransition new]; 
          [self.navigationController dismissViewControllerAnimated:YES completion:nil]; 
         } 
        } 
        else if (gestureRecognizer.state == UIGestureRecognizerStateChanged) 
        { 
         CGFloat percent = [gestureRecognizer translationInView:view].x/CGRectGetWidth(view.bounds); 
         [self.interactionController updateInteractiveTransition:percent]; 
        } 
        else if (gestureRecognizer.state == UIGestureRecognizerStateEnded) 
        { 
         CGFloat percent = [gestureRecognizer translationInView:view].x/CGRectGetWidth(view.bounds); 
         if (percent > 0.5 || [gestureRecognizer velocityInView:view].x > 50) 
         { 
          [self.interactionController finishInteractiveTransition]; 
         } 
         else 
         { 
          [self.interactionController cancelInteractiveTransition]; 
         } 
         self.interactionController = nil; 
        } 
    } 
    
    #pragma mark - <UIViewControllerAnimatedTransitioning> 
    
    - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)__unused presented presentingController:(UIViewController *)__unused presenting sourceController:(UIViewController *)__unused source 
    { 
        TransitionAnimator *animator = [TransitionAnimator new]; 
        animator.appearing = YES; 
        return animator; 
    } 
    
    - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)__unused dismissed 
    { 
        TransitionAnimator *animator = [TransitionAnimator new]; 
        return animator; 
    } 
    
    - (id<UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id<UIViewControllerAnimatedTransitioning>)__unused animator 
    { 
        return nil; 
    } 
    
    - (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)__unused animator 
    { 
    #pragma clang diagnostic push 
    #pragma clang diagnostic ignored "-Wgnu-conditional-omitted-operand" 
        return self.interactionController ?: nil; 
    #pragma clang diagnostic pop 
    } 
    

    代表將提供其動畫師,交互控制器控制器,和管理在屏幕邊緣平移手勢關閉的模態介紹。

  2. 在Storyboard中,將對象(黃色立方體)從對象庫拖動到模態導航控制器。將其課程設置爲我們的NavigationControllerDelegate,並將其delegatenavigationController插座連接到故事板的模式導航控制器。

  3. 在您的搜索結果控制器的prepareForSegue中,您需要設置模態導航控制器的過渡委託和模態演示樣式。

    navigationController.transitioningDelegate = (id<UIViewControllerTransitioningDelegate>)navigationController.delegate; 
    navigationController.modalPresentationStyle = UIModalPresentationCustom; 
    

    模式演示文稿執行的自定義動畫由過渡動畫處理。

  4. 在Xcode中,爲您的項目添加一個新的TransitionAnimator文件。

    TransitionAnimator.h:

    @interface TransitionAnimator : NSObject <UIViewControllerAnimatedTransitioning> 
    @property (nonatomic, assign, getter = isAppearing) BOOL appearing; 
    

    TransitionAnimator.m:

    @implementation TransitionAnimator 
    @synthesize appearing = _appearing; 
    
    #pragma mark - <UIViewControllerAnimatedTransitioning> 
    
    - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext 
    { 
        return 0.3; 
    } 
    
    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext 
    { 
        // Custom animation code goes here 
    } 
    

動畫代碼太長,答案中提供,但它在a sample project which I've shared on GitHub的可用。

說了這話之後,代碼就像是一個有趣的練習。蘋果已經有數年時間來完善和支持他們的所有轉變。如果您採用這種自定義動畫,您可能會發現某些情況(例如可見鍵盤),動畫不會執行Apple的操作。您必須決定是否要花時間改善代碼以妥善處理這些情況。

+0

有趣。我希望這會比這更簡單。 :)看起來像很多工作來僞造一個導航控制器。您不僅需要創建動畫,還需要交互式轉換滑動手勢。 –

+0

這個互動部分並不像看起來那麼複雜。 Ash Furrow在幾年前寫了一篇文章[Custom UIViewController Transitions](http://www.teehanlax.com/blog/custom-uiviewcontroller-transitions/)文章解釋它,並且在GitHub上還有其他的例子。 –

+0

謝謝,我來看看。所以我想這是一個自定義的模式轉換,看起來像是推送轉換。 –

1

當您呈現viewController時,navigationController變爲不可用。所以你必須首先解除你的模式,然後推另一個viewController。

+0

但這不是日曆所做的或看起來像它在做什麼。當我從結果選擇返回時,我仍然看到搜索欄,它並沒有被解僱。 –

1

UISearchController必須是UINavigationController的rootViewController,然後將導航控制器作爲模式呈現。

+0

我嘗試過,但不幸的是,我根本沒有看到搜索欄,視圖淡入而不是動畫。 –

2

編輯:不依賴於UIWindow的替代解決方案。我認爲效果與日曆應用非常相似。

@interface SearchResultsController() <UINavigationControllerDelegate> 
@end 

@implementation SearchResultsController 

- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    // this will be the UINavigationController that provides the push animation. 
    // its rootViewController is a placeholder that exists so we can actually push and pop 
    UIViewController* rootVC = [UIViewController new]; // this is the placeholder 
    rootVC.view.backgroundColor = [UIColor clearColor]; 
    UINavigationController* nc = [[UINavigationController alloc] initWithRootViewController: rootVC]; 
    nc.modalPresentationStyle = UIModalPresentationCustom; 
    nc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; 

    [UIView transitionWithView: self.view.window 
         duration: 0.25 
         options: UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowAnimatedContent 
        animations: ^{ 

         [self.parentViewController presentViewController: nc animated: NO completion: ^{ 

          UIViewController* resultDetailViewController = [UIViewController alloc]; 
          resultDetailViewController.title = @"Result Detail"; 
          resultDetailViewController.view.backgroundColor = [UIColor whiteColor]; 

          [nc pushViewController: resultDetailViewController animated: YES]; 
         }]; 
        } 
        completion:^(BOOL finished) { 

         nc.delegate = self; 
        }]; 
} 

- (void) navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated 
{ 
    // pop to root? then dismiss our window. 
    if (navigationController.viewControllers[0] == viewController) 
    { 
     [UIView transitionWithView: self.view.window 
          duration: [CATransaction animationDuration] 
          options: UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowAnimatedContent 
         animations: ^{ 

          [self.parentViewController dismissViewControllerAnimated: YES completion: nil]; 
         } 
         completion: nil]; 
    } 
} 

@end 

原來的解決方案:

這裏是我的解決方案。我開始了使用您在UICatalog例如發現了顯示搜索控制器相同的技術:

- (IBAction)search:(id)sender 
{ 
    SearchResultsController* searchResultsController = [self.storyboard instantiateViewControllerWithIdentifier: @"SearchResultsViewController"]; 

    self.searchController = [[UISearchController alloc] initWithSearchResultsController:searchResultsController]; 
    self.searchController.hidesNavigationBarDuringPresentation = NO; 


    [self presentViewController:self.searchController animated:YES completion: nil]; 
} 

在我的例子,SearchResultsController是一個UITableViewController派生類。當搜索結果被點擊時,它會創建一個帶有根UINavigationController的新UIWindow,並將結果 - 詳細視圖控制器推送到該UIWindow。它監視UINavigationController彈出到根目錄,以便它可以關閉特殊的UIWindow。

現在,UIWindow並不是嚴格要求的。我使用它,因爲它有助於在推送/彈出過渡期間保持SearchViewController可見。相反,您可以僅從UISearchController提供UINavigationController(並從navigationController:didShowViewController:delegate方法中將其解除)。但默認情況下呈現在視圖控制器上的視圖控制器隱藏了下面的內容。你可以通過編寫一個自定義的轉換來解決這個問題,這個轉換將作爲UINavigationController的transitioningDelegate應用。

@interface SearchResultsController() <UINavigationControllerDelegate> 
@end 

@implementation SearchResultsController 
{ 
    UIWindow* _overlayWindow; 
} 

- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    // this will be the UINavigationController that provides the push animation. 
    // its rootViewController is a placeholder that exists so we can actually push and pop 
    UINavigationController* nc = [[UINavigationController alloc] initWithRootViewController: [UIViewController new]]; 

    // the overlay window 
    _overlayWindow = [[UIWindow alloc] initWithFrame: self.view.window.frame]; 
    _overlayWindow.rootViewController = nc; 
    _overlayWindow.windowLevel = self.view.window.windowLevel+1; // appear over us 
    _overlayWindow.backgroundColor = [UIColor clearColor]; 
    [_overlayWindow makeKeyAndVisible]; 

    // get this into the next run loop cycle: 
    dispatch_async(dispatch_get_main_queue(), ^{ 

     UIViewController* resultDetailViewController = [UIViewController alloc]; 
     resultDetailViewController.title = @"Result Detail"; 
     resultDetailViewController.view.backgroundColor = [UIColor whiteColor]; 
     [nc pushViewController: resultDetailViewController animated: YES]; 

     // start looking for popping-to-root: 
     nc.delegate = self; 
    }); 
} 

- (void) navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated 
{ 
    // pop to root? then dismiss our window. 
    if (navigationController.viewControllers[0] == viewController) 
    { 
     [_overlayWindow resignKeyWindow]; 
     _overlayWindow = nil; 
    } 
} 

@end 
+0

我沒有想過使用UIWindow。我稍後再試。雖然我擔心它不會有與日曆應用程序相同的交互式轉換,其中導航欄以與其餘視圖不同的速率移動。 –

+0

感謝您的兩種解決方案。對於新解決方案來說,它是很好的,因爲它在彈出時通過褪色進行動畫,而不是僅僅是掀起,但是它提出了一些問題,例如交互式刷卡手勢沒有正確取消,並且突然解散到第一個VC而不是結果VC。 –

4

我知道這個線程是舊的,但似乎有一個更簡單的方法來獲得所需的行爲。

要實現的重要事情是UISearchController是從源控制器,它是導航控制器內的視圖控制器呈現。如果檢查視圖層次結構,則會發現搜索控制器與常規模態演示不同,不是作爲窗口的直接子對象顯示,而是作爲導航控制器的子視圖顯示。

所以一般結構是

  • 的UINavigationController
    • MyRootViewController
      • UISearchViewController(呈現僞 「模態」)
          MyContentController

基本上你只需要從MyContentController起牀到MyRootViewController,這樣你就可以訪問它的navigationController屬性。在我的搜索內容控制器的我的tableView:didSelectRowAtIndexPath:方法中,我只需使用以下命令訪問我的根視圖控制器。

UINavigationController *navigationController = nil; 
if ([self.parentViewController isKindOfClass:[UISearchController class]]) { 
    navigationController = self.parentViewController.presentingViewController.navigationController; 
} 

從那裏你可以輕鬆地將東西推到導航控制器上,動畫就是你所期望的。

+0

這不適合我。什麼都沒發生。你有一個樣本項目,或者你想看看我的? –

+0

其實我忘了檢查我是否得到navVC。原來我需要'navigationController = self.parentViewController.presentingViewController;而不是「 」。儘管在推動風險投資時它仍然沒有做任何事情。 –

+0

看起來有點哈克。但我認爲它比自己做所有的動畫更優雅。做得好! – d4Rk

相關問題