【问题标题】:How to ensure that View Controller does not reload when segueing?segueing时如何保证View Controller不重新加载?
【发布时间】:2013-11-19 22:41:49
【问题描述】:

我已经尝试了几种开源滑动菜单导航,它们模拟 Facebook、Gmail 应用程序中的导航菜单(ViewDeckMFSideMenuSWRevealViewController),并且遇到了与我 segue 时相同的问题进入另一个基于菜单选择的 ViewController,然后回到原来的 VC,你的 VC 总是重新加载,所以之前以编程方式添加的任何子视图都消失了。即使我只是从菜单中选择相同的 VC,情况也是如此——它仍然会被加载。我试图在 App Delegate 中保留指向视图控制器的强指针(通过实例化该视图的属性),但这并没有解决问题?还有另一种解决方法吗?谢谢!

我目前正在使用 SWRevealViewController,并将我的 Storyboard 设置为这样。

更新和什么对我有用:

最终,一切都符合 MFSideMenu 和 danh 的建议,以将我感兴趣的控制器保留在导航堆栈中。

这是我选择非主视图控制器的代码

- (IBAction)buttonForNonPrimaryVC:(UIButton *)sender
{

    someVC *someVC = [self.storyboard instantiateViewControllerWithIdentifier:@"someIdentifier"];
    UINavigationController *navigationController = self.menuContainerViewController.centerViewController;
    [navigationController pushViewController:someVC animated:NO];
    [self.menuContainerViewController setMenuState:MFSideMenuStateClosed];

}

对于主视图控制器的选择:

- (IBAction)primaryVCTapped:(UIButton *)sender {

    if ([[[self.menuContainerViewController.centerViewController viewControllers] lastObject] isKindOfClass: [primaryVC class]] || [[self.menuContainerViewController.centerViewController viewControllers] count] < 2){
        //Do nothing
    } else {
        [self.menuContainerViewController.centerViewController popViewControllerAnimated:NO];
    }
    [self.menuContainerViewController setMenuState:MFSideMenuStateClosed];
}

【问题讨论】:

    标签: ios iphone uiviewcontroller uistoryboard


    【解决方案1】:

    特别是对于 SWRevealViewController,重用视图控制器的实例实际上非常简单,并且不需要修改 SWRevealViewController。

    在您指定的 MenuViewController(当您希望显示菜单项的视图控制器时负责调用 segues 的视图控制器)中,创建视图控制器缓存。这将用于在通过 segue 创建视图控制器实例时存储它们:

    @property (nonatomic, strong) NSMutableDictionary *viewControllerCache;
    

    我们稍后会在需要时对其进行初始化。

    每当您响应选择菜单项时,而不是直接调用 segue,而是调用一个方法来检查缓存:

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
    
        // your logic will vary here, this is just an example
    
        switch(indexPath.row)
        {
            case 0:
                [self showViewControllerForSegueWithIdentifier:@"showSomething" sender:nil];
                break;
            default:
                break;
        }
    
    }
    

    这种缓存检查方法可能如下所示:

    - (void)showViewControllerForSegueWithIdentifier:(NSString *)identifier sender:(id)sender
    {
    
        NSString *cacheKey = [identifier stringByAppendingFormat:@":%@", sender];
        UIViewController *dvc = [self.viewControllerCache objectForKey:cacheKey];
        if(dvc)
        {
            NSLog(@"reusing view controller from cache");
            UINavigationController* navController = (UINavigationController*)self.revealViewController.frontViewController;
            [navController setViewControllers: @[dvc] animated: NO ];
            [self.revealViewController setFrontViewPosition: FrontViewPositionLeft animated: YES];
        }
        else
        {
            NSLog(@"creating view controller from segue");
            [self performSegueWithIdentifier:identifier sender:sender];
        }
    
    }
    

    在这种情况下,我在缓存中创建一个键作为 segue 名称和 sender 参数的组合。为了举例,我假设发件人是一个字符串。 sender 参数很可能不是字符串,因此您可能应该进行一些检查以确保它不会崩溃。

    然后我检查视图控制器缓存是否存在任何视图控制器。如果是这样,请执行您通常在prepareForSegue:sender: 中执行的视图控制器交换(当您第一次设置 SWRevealViewController 时,您会在其中粘贴此 sn-p)。如果它不存在,则照常执行 segue(这将创建一个新实例)。

    现在剩下的就是修改您的prepareForSegue:sender: 方法以在缓存中存储对实例化视图控制器的引用:

    - (void) prepareForSegue:(UIStoryboardSegue *) segue sender:(id) sender
    {
    
        if([segue.identifier isEqualToString:@"showSomething"])
        {
            // do whatever you wish to the destination view controller here
            // ...
    
        }
    
        // this part should be very familiar
        if ( [segue isKindOfClass: [SWRevealViewControllerSegue class]] )
        {
    
            SWRevealViewControllerSegue *swSegue = (SWRevealViewControllerSegue*) segue;
    
            swSegue.performBlock = ^(SWRevealViewControllerSegue* rvc_segue, UIViewController* svc, UIViewController* dvc) {
    
                // cache the view controller
                if(self.viewControllerCache == nil) self.viewControllerCache = [NSMutableDictionary dictionary];
                NSString *cacheKey = [segue.identifier stringByAppendingFormat:@":%@", sender];
                [self.viewControllerCache setObject:dvc forKey:cacheKey];
    
                // the rest remains as before
                UINavigationController* navController = (UINavigationController*)self.revealViewController.frontViewController;
                [navController setViewControllers: @[dvc] animated: NO ];
                [self.revealViewController setFrontViewPosition: FrontViewPositionLeft animated: YES];
            };
    
        }
    }
    

    请注意,我只是在prepareForSegue:sender: 中添加了 2-3 行,这不会干扰您现有的设置。可能没有设置 segue 标识符,这将导致崩溃。您应该在 segues 上使用标识符,以便识别它们以进行缓存。

    这种方法的一个限制是它只缓存从菜单视图控制器中使用 segues 调用的视图控制器。您会注意到为第一个可见视图控制器选择菜单项将导致它从头开始重新加载(因为它尚未缓存)。此后的任何时候,它都应该与缓存一起使用。我想将缓存转移到位于情节提要中的菜单设置之前的 SWRevealController 将缓解这种情况。你可以通过继承它来做到这一点。

    【讨论】:

    • 实际上 SWRevealViewControllerSegue 已被弃用。您知道如何在新 API 中实现这种解决方案吗?根据我所见,我们无法在新 API 中访问 PushSegue 或 SetSegue 的 performBlock。
    • @LoryHuz 我不能说我愿意 - 几个月后我放弃了 SWRevealViewController,转而创建自己的侧边菜单。
    【解决方案2】:

    我有一个客户想使用 MFSideMenu,我使用的解决方案是放置一个 UINavigationVC 作为中心 vc。该应用程序使用弹出和推送来导航,而不是 segues。这具有在用户与推送视图控制器交互时在导航堆栈中维护应用程序的主视图控制器的理想行为。

    The side menus are table views, so when a table row gets selected, a message is sent to the center vc's root vc and it does basically this:

    - (void)sideMenu:(MFSideMenuContainerViewController *)menuVC didSelectItemAtIndex:(NSInteger)index {
    
        // we keep an array of vs classes to instantiate for each menu item
        Class *klass = self.menuVCClasses[index];
    
        // client also wanted each vc in it's own nib.  I would have preferred storyboard, but...
        NSString *nibName = NSStringFromClass(klass);
        UIViewController *vc = [[klass alloc] initWithNibName:nibName bundle:[NSBundle mainBundle]];
    
        // or from storyboard
        UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass(klass)]; 
    
        // here's the punch line.  pop without animation, push with animation
        // this will keep the main vc around at the bottom of the stack
        [self.navigationController popToRootViewControllerAnimated:NO];
        [self.navigationController pushViewController:vc animated:YES];
    }
    

    【讨论】:

    • 感谢分享这个想法!只是为了确保我理解,而不是将您的控制器设置为实例化的东西(即不是controller = [self.storyboard instantiateViewControllerWithIdentifier:@"IdOfYourViewControllerA"];,而是只需执行推送动画,当导航回您的“主”控制器时,您将popToRootViewController动画设置为否?
    • 是的,但是您也可以从情节提要中获取 vc。我会在答案中说明。
    • 那太好了!如果您可以发布有关如何设置 Storyboard 的基本屏幕截图(或草图),那就太好了 - 谢谢!
    • 看...它在那里,“vc”的替代初始化。该代码取决于注释掉其他基于 nib 的行,并为 vc 提供一个与其类名相同的故事板 ID。
    • 是的。它是 UIViewController 类的数组,初始化如下 ...arrayWithObjects:[VCClassA self], [VCClassB self], nil];它必须在用户从菜单中选择之前的某个时间进行初始化。
    【解决方案3】:

    当你进行 segue 时,总是会创建一个新的视图控制器实例。使用 .xib 文件而不是情节提要可以让您重用视图控制器并保持其他视图处于活动状态。

    【讨论】:

    • 您也可以从情节提要中显式实例化,而不是使用 segue。
    • @Wain 当你说“显式实例化”时,你的意思是我会做类似controller = [self.storyboard instantiateViewControllerWithIdentifier:@"IdOfYourViewControllerA"]; 的事情,然后将那个视图控制器推送到导航堆栈,同时摆脱我放在情节提要中的segues?谢谢!
    【解决方案4】:

    另一种解决方案是在您的侧边菜单控制器中添加一个委托并仅实现

    在用户单击时导航到的视图控制器中委托。

    【讨论】:

      猜你喜欢
      • 2015-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-18
      • 1970-01-01
      相关资源
      最近更新 更多