繁体   English   中英

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

[英]How do I push a view controller onto the nav stack from search results when presenting them modally?

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

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

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

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

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

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

更新:

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

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

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

我可以看到,有三种效果需要重复。

  1. 当出现的视图出现时,底层内容会变暗。
  2. 呈现的视图有阴影。
  3. 底层内容的导航完全在屏幕外动画,但其内容部分动画。

我在交互式自定义模态转换中重现了一般效果。 它通常模仿日历的动画,但有一些差异(未显示),例如键盘(重新)出现太快。

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

在此输入图像描述

以下是涉及的步骤:

  1. 在故事板中,您可以将“ 显示详细信息”中的“Segue”类型更改为“ 模态 显示”

您可以将PresentationTransition设置为Default,因为它们需要在代码中被覆盖。

  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插座连接到storyboard的模态导航控制器。

  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 } 

动画代码太长而无法在答案中提供,但它在我在GitHub上共享的示例项目中可用

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

我知道这个线程已经老了,但似乎有一个更简单的方法来获得所需的行为。

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

所以一般结构是

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

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

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

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

编辑 :一种不依赖于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弹出到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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM