简体   繁体   English

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

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

I want to recreate the search UI shown in the iOS 7/8 calendar app. 我想重新创建iOS 7/8日历应用程序中显示的搜索UI。 Presenting the search UI modally isn't a problem. 以模态方式呈现搜索UI不是问题。 I use UISearchController and modally present it just like the UICatalog sample code shows which gives me a nice drop down animation. 我使用UISearchController并以模态方式呈现它,就像UICatalog示例代码显示的那样,它给了我一个很好的下拉动画。 The issue comes when trying to push a view controller from the results view controller. 尝试从结果视图控制器推送视图控制器时出现问题。 It isn't wrapped in a navigation controller so I can't push onto it. 它没有包裹在导航控制器中,所以我无法推动它。 If I do wrap it in a navigation controller then I don't get the default drop down animation when I present the UISearchController . 如果我将它包装在导航控制器中,那么当我呈现UISearchController时,我没有得到默认的下拉动画。 Any ideas? 有任何想法吗?

EDIT: I got it to push by wrapping my results view controller in a nav controller. 编辑:我通过将结果视图控制器包装在导航控制器中来推动它。 However the search bar is still present after pushing the new VC onto the stack. 但是,在将新VC推入堆栈后,搜索栏仍然存在。

EDIT (2): DTS from Apple said that the calendar app uses a non-standard method to push from search results. 编辑(2):Apple的DTS表示日历应用程序使用非标准方法从搜索结果中推送。 Instead they recommend removing focus from the search controller then pushing and returning focus on pop. 相反,他们建议从搜索控制器中移除焦点,然后将焦点推向并返回pop。 This is similar to the way search in the settings app works I imagine. 这类似于我想象的设置应用程序中的搜索方式。

Apple has gotten very clever there, but it's not a push, even though it looks like one. 苹果在那里变得非常聪明,但它并不是一种推动,即使它看起来像一个。

They're using a custom transition (similar to what a navigation controller would do) to slide in a view controller which is embedded in a navigation controller. 他们使用自定义转换(类似于导航控制器所做的)在嵌入导航控制器的视图控制器中滑动。

You can spot the difference by slowly edge-swiping that detail view back and letting the previous view start to appear. 您可以通过缓慢地向后滑动该详细视图并让前一个视图开始出现来发现差异。 Notice how the top navigation slides off to the right along with the details, instead of its bar buttons and title transitioning in-place? 请注意顶部导航如何与细节一起滑动到右侧,而不是其栏位按钮和标题过渡到位?

Update: 更新:

The problem that you're seeing is that the search controller is presented above your navigation controller. 您看到的问题是搜索控制器出现在导航控制器上方。 As you discovered, even if you push a view controller onto a navigation controller's stack, the navigation bar is still beneath the search controller's presentation, so the search bar obscures any (pushed view controller's) navigation bar. 正如您所发现的,即使您将视图控制器推到导航控制器的堆栈上,导航栏仍然位于搜索控制器的演示文稿下方,因此搜索栏会遮挡任何(推送视图控制器)导航栏。

If you want to show results on top of the search controller without dismissing it, you'll need to present your own modal navigation view controller. 如果要在搜索控制器顶部显示结果而不解除它,则需要提供自己的模态导航视图控制器。

Unfortunately, there's no transition style which will let you present your navigation controller the same way the built-in push animation behaves. 不幸的是,没有过渡风格可以让你以与内置推动画动作相同的方式呈现你的导航控制器。

As I can see, there are three effects that need to be duplicated. 我可以看到,有三种效果需要重复。

  1. The underlying content dims, as the presented view appears. 当出现的视图出现时,底层内容会变暗。
  2. The presented view has a shadow. 呈现的视图有阴影。
  3. The underlying content's navigation completely animates off-screen, but its content partially animates. 底层内容的导航完全在屏幕外动画,但其内容部分动画。

I've reproduced the general effect within an interactive custom modal transition. 我在交互式自定义模态转换中重现了一般效果。 It generally mimic's Calendar's animation, but there are some differences (not shown), such as the keyboard (re)appearing too soon. 它通常模仿日历的动画,但有一些差异(未显示),例如键盘(重新)出现太快。

The modal controller that's presented is a navigation controller. 提供的模态控制器是导航控制器。 I wired up a back button and edge swipe gesture to (interactively) dismiss it. 我连接了一个后退按钮和边缘滑动手势(以交互方式)将其关闭。

在此输入图像描述

Here are the steps that are involved: 以下是涉及的步骤:

  1. In your Storyboard, you would change the Segue type from Show Detail to Present Modally . 在故事板中,您可以将“ 显示详细信息”中的“Segue”类型更改为“ 模态 显示”

You can leave Presentation and Transition set to Default, as they'll need to be overridden in code. 您可以将PresentationTransition设置为Default,因为它们需要在代码中被覆盖。

  1. In Xcode, add a new NavigationControllerDelegate file to your project. 在Xcode中,将新的NavigationControllerDelegate文件添加到项目中。

    NavigationControllerDelegate.h: NavigationControllerDelegate.h:

     @interface NavigationControllerDelegate : NSObject <UINavigationControllerDelegate> 

    NavigationControllerDelegate.m: 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 } 

    The delegate will provide the controller with its animator, interaction controller, and manage the screen edge pan gesture to dismiss the modal presentation. 代表将为控制器提供其动画师,交互控制器,并管理屏幕边缘平移手势以消除模态演示。

  2. In Storyboard, drag an Object (yellow cube) from the object library to the modal navigation controller. 在Storyboard中,将对象 (黄色立方体)从对象库拖动到模态导航控制器。 Set its class to our NavigationControllerDelegate , and wire up its delegate and navigationController outlets to the storyboard's modal navigation controller. 将其类设置为我们的NavigationControllerDelegate ,并将其delegatenavigationController插座连接到storyboard的模态导航控制器。

  3. In prepareForSegue from your search results controller, you'll need to set the modal navigation controller's transitioning delegate and modal presentation style. 在您的搜索结果控制器的prepareForSegue ,您需要设置模态导航控制器的转换委托和模态演示样式。

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

    The custom animation that the modal presentation performs is handled by transition animator. 模态演示文稿执行的自定义动画由过渡动画师处理。

  4. In Xcode, add a new TransitionAnimator file to your project. 在Xcode中,将新的TransitionAnimator文件添加到项目中。

    TransitionAnimator.h: TransitionAnimator.h:

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

    TransitionAnimator.m: 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 } 

The animation code is too long to provide within an answer, but it's available in a sample project which I've shared on GitHub . 动画代码太长而无法在答案中提供,但它在我在GitHub上共享的示例项目中可用

Having said this, the code, as it stands, was more of a fun exercise. 话虽如此,但现在的代码更像是一种有趣的练习。 Apple has had years to refine and support all their transitions. Apple已经有多年的时间来完善和支持他们的所有转换。 If you adopt this custom animation, you may find cases (such as the visible keyboard) where the animation doesn't do what Apple's does. 如果您采用此自定义动画,您可能会发现动画与Apple不同的情况(例如可见键盘)。 You'll have to decide whether you want to invest the time to improve the code to properly handle those cases. 您必须决定是否要花时间来改进代码以正确处理这些情况。

I know this thread is old, but there seems to be a much simpler approach to getting the desired behavior. 我知道这个线程已经老了,但似乎有一个更简单的方法来获得所需的行为。

The important thing to realize is the UISearchController is presented from the source controller, which is a view controller inside the navigation controller. 要实现的重要一点是UISearchController是从源控制器呈现的,源控制器是导航控制器内部的视图控制器。 If you inspect the view hierarchy, you see that the search controller, unlike regular modal presentations, isn't presented as a direct child of the window, but rather as a subview of the navigation controller. 如果检查视图层次结构,您会发现搜索控制器与常规模式演示不同,不是作为窗口的直接子项呈现,而是作为导航控制器的子视图。

So the general structure is 所以一般结构是

  • UINavigationController UINavigationController的
    • MyRootViewController MyRootViewController
      • UISearchViewController (presented pseudo-"modally") UISearchViewController(呈现伪“模态”)
        • MyContentController MyContentController

Essentially you just need to get from the MyContentController up to the MyRootViewController, so you can access its navigationController property. 基本上你只需要从MyContentController到MyRootViewController,这样你就可以访问它的navigationController属性。 In my tableView:didSelectRowAtIndexPath: method of my search content controller, I simply use the following to access my root view controller. 在我的tableView:didSelectRowAtIndexPath:我的搜索内容控制器的方法,我只是使用以下来访问我的根视图控制器。

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

From there you can easily push something onto the navigation controller, and the animation is exactly what you'd expect. 从那里你可以轻松地将某些东西推到导航控制器上,动画正是你所期望的。

EDIT : an alternate solution that doesn't rely on a UIWindow . 编辑 :一种不依赖于UIWindow的替代解决方案。 I think the effect is very similar to the calendar app. 我认为效果与日历应用非常相似。

@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 solution: 原始解决方案:

Here's my solution. 这是我的解决方案。 I start out using the same technique you discovered in the UICatalog example for showing the search controller: 我开始使用您在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];
}

In my example, SearchResultsController is a UITableViewController-derived class. 在我的示例中,SearchResultsController是一个UITableViewController派生类。 When a search result is tapped it creates a new UIWindow with a root UINavigationController and pushes the result-detail view controller to that. 当点击搜索结果时,它会创建一个带有根UINavigationController的新UIWindow,并将结果详细信息视图控制器推送到该UIWindow。 It monitors for the UINavigationController popping to root so it can dismiss the special UIWindow. 它监视UINavigationController弹出到root,以便它可以解除特殊的UIWindow。

Now, the UIWindow isn't strictly required. 现在,UIWindow并不是严格要求的。 I used it because it helps keep the SearchViewController visible during the push/pop transition. 我使用它是因为它有助于在推/弹转换期间保持SearchViewController可见。 Instead, you could just present the UINavigationController from the UISearchController (and dismiss it from the navigationController:didShowViewController: delegate method). 相反,您可以从UISearchController呈现UINavigationController(并从navigationController中删除它:didShowViewController:delegate方法)。 But modally-presented view controllers present on an opaque view by default, hiding what's underneath. 但默认情况下,模态呈现的视图控制器出现在不透明视图中,隐藏了下面的内容。 You could address this by writing a custom transition that would be applied as the UINavigationController's transitioningDelegate. 您可以通过编写将作为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

As you present a viewController the navigationController becomes unavailable. 当您呈现viewController时,navigationController变得不可用。 So you have to dismiss your modal first and then push another viewController. 所以你必须首先解雇你的模态,然后推送另一个viewController。

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

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

相关问题 在导航堆栈中模态显示视图控制器 - Presenting a view controller modally within a navigation stack 模态呈现视图控制器 - Presenting a View Controller Modally Swift:以模态方式呈现视图 controller 时更改背景颜色 - Swift: background color is changed when presenting view controller modally 模态显示所选表行中的新视图控制器 - Presenting a new view controller from selected table row modally 模态展示另一个控制器时,如何完全关闭视图控制器的所有后台工作 - How to fully close all background work of a view controller when modally presenting another one 从呈现视图控制器推送视图控制器 - Push View Controller from Presenting View Controller 呈现视图控制器以模态重置当前视图 - Presenting a View Controller Modally Resets The Current View 如何在执行popToRootViewController后将视图控制器推送到导航堆栈 - How to push a view controller onto navigation stack AFTER executing popToRootViewController 模态呈现控制器时,如何保持UINavigationController的UINavigationBar透明? - How to keep UINavigationController's UINavigationBar transparent when modally presenting a controller? 问题以模态呈现具有搜索显示控制器的表视图控制器并返回所选单元格 - issue modally presenting a table view controller that has a search display controller and returning the selected cell
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM