在以模态方式呈现视图控制器时,如何将视图控制器从搜索结果推入导航堆栈?

时间:2015-08-28 19:44:34

标签: ios ios8 uinavigationcontroller uisearchcontroller

我想重新创建iOS 7/8日历应用中显示的搜索UI。以模态方式呈现搜索UI不是问题。我使用UISearchController并以模态方式呈现它,就像UICatalog示例代码显示的那样,它给了我一个很好的下拉动画。尝试从结果视图控制器推送视图控制器时出现问题。它没有包裹在导航控制器中,所以我无法推动它。如果我将它包装在导航控制器中,那么当我出现UISearchController时,我不会获得默认的下拉动画。有什么想法吗?

编辑: 我通过将结果视图控制器包装在导航控制器中来实现它。但是,在将新VC推入堆栈后,搜索栏仍然存在。

编辑(2): 来自Apple的DTS表示,日历应用程序使用非标准方法从搜索结果中推送。相反,他们建议从搜索控制器中移除焦点,然后将焦点推向并返回pop。这类似于我想象的设置应用程序中的搜索方式。

5 个答案:

答案 0 :(得分:12)

Apple在那里变得非常聪明,但它并不是一种推动,即使它看起来像是一种。

他们使用自定义转换(类似于导航控制器的操作)在嵌入导航控制器的视图控制器中滑动。

您可以通过缓慢地向后滑动该详细视图并让上一个视图开始出现来发现差异。请注意顶部导航如何与细节一起滑动到右侧,而不是其栏位按钮和标题过渡到位?

<强>更新

您看到的问题是搜索控制器显示在导航控制器上方。正如您所发现的,即使您将视图控制器推到导航控制器的堆栈上,导航栏仍然位于搜索控制器的演示文稿下方,因此搜索栏会遮挡任何(推送的视图控制器)导航栏。

如果您希望在搜索控制器顶部显示结果而不解除它,则需要提供自己的模态导航视图控制器。

不幸的是,没有过渡风格可以让你以与内置推送动画相同的方式呈现你的导航控制器。

正如我所看到的,有三种效果需要重复。

  1. 当呈现的视图出现时,基础内容会变暗。
  2. 呈现的视图有阴影。
  3. 基础内容的导航完全在屏幕外动画,但其内容部分动画。
  4. I've reproduced the general effect within an interactive custom modal transition.它通常模仿日历的动画,但存在一些差异(未显示),例如键盘(重新)出现得太快。

    呈现的模态控制器是导航控制器。我连接了一个后退按钮和边缘滑动手势(以交互方式)关闭它。

    enter image description here

    以下是涉及的步骤:

    1. 在故事板中,您可以将Segue类型从显示详细信息更改为以模态显示
    2. 您可以将演示文稿转换设置为默认值,因为它们需要在代码中被覆盖。

      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
        }
        
      5. 动画代码太长,无法在答案中提供,但它在a sample project which I've shared on GitHub中可用。

        说到这里,代码就像现在一样,更像是一种有趣的练习。 Apple已经有多年的时间来完善和支持他们的所有转换。如果您采用此自定义动画,您可能会发现动画与Apple不同的情况(例如可见键盘)。您必须决定是否要花时间改进代码以正确处理这些情况。

答案 1 :(得分:6)

我知道这个帖子已经老了,但似乎有一个更简单的方法来获得理想的行为。

要实现的重要一点是UISearchController是从源控制器呈现的,源控制器是导航控制器内部的视图控制器。如果检查视图层次结构,您会发现搜索控制器与常规模态演示不同,不是作为窗口的直接子项呈现,而是作为导航控制器的子视图。

所以一般结构是

  • 的UINavigationController
    • MyRootViewController
      • UISearchViewController(呈现为伪“模态”)
        • MyContentController

基本上你只需要从MyContentController到MyRootViewController,这样你就可以访问它的navigationController属性。在我的搜索内容控制器的tableView:didSelectRowAtIndexPath:方法中,我只是使用以下内容来访问我的根视图控制器。

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

从那里你可以轻松地将某些东西推到导航控制器上,动画正是你所期望的。

答案 2 :(得分: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

ORIGINAL解决方案:

这是我的解决方案。我开始使用您在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弹出到root,以便它可以关闭特殊的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

答案 3 :(得分:1)

当您呈现viewController时,navigationController变得不可用。所以你必须首先解雇你的模态,然后推送另一个viewController。

答案 4 :(得分:1)

UISearchController必须是UINavigationController的rootViewController,然后将导航控制器呈现为模态。