而不是推segue如何替换视图控制器(或从导航堆栈中删除)?


68

我有一个小型iPhone应用程序,它使用导航控制器显示3个视图(此处为全屏):

Xcode屏幕截图

首先,它显示一个社交网络列表(Facebook,Google +等):

列表截图

然后显示一个OAuth对话框,要求提供凭据:

登录屏幕截图

并且(之后,在同一个中UIWebView)获得权限:

权限截图

最后,它显示带有用户详细信息的最后一个视图控制器(在实际应用中,这将是菜单,可以在其中启动多人游戏):

细节截图

这一切都很好,但是当用户想要返回并选择另一个社交网络时,我遇到了一个问题:

用户触摸后退按钮,而不是显示第一个视图,而是显示第二个视图,再次询问OAuth凭据/权限。

我在这里可以做什么?Xcode中5.0.2所示为塞格斯非常有限的选择-莫代尔(我不能使用,因为它掩盖了需要我的游戏导航栏)和自定义

我是iOS编程的新手,但较早前我开发了Adobe AIR移动应用程序,可以1)替换视图而不是推动视图,以及2)从导航堆栈中删除不需要的视图。

请问如何在本地应用程序中执行相同操作?

Answers:


38

您可以使用自定义segue:要做到这一点,您需要创建一个子类化UIStoryboardSegue的类(例如MyCustomSegue),然后可以使用类似的方法覆盖“ perform”

-(void)perform {
    UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
    UIViewController *destinationController = (UIViewController*)[self destinationViewController];
    UINavigationController *navigationController = sourceViewController.navigationController;
    // Pop to root view controller (not animated) before pushing
    [navigationController popToRootViewControllerAnimated:NO];
    [navigationController pushViewController:destinationController animated:YES];    
}

此时,转到Interface Builder,选择“ custom” segue,然后输入您的类的名称(例如MyCustomSegue)


谢谢,但是您确定吗(使用第三个场景替换第二个场景-而不是按下-从而使“返回”按钮返回到第一个场景)可以通过使用自定义序列来实现?我正在阅读developer.apple.com/library/ios/documentation/UIKit/Reference/…,但不知道该怎么做。
亚历山大·法伯

1
在您的第一个建议中,您没有保存sourceViewController.navigationController到变量,而是nil在执行最后一条命令之前将其转至perform-这就是为什么它对我不起作用的原因。但是当前版本运行良好,谢谢!Lauro和Bhavya的答案也很有见地。
亚历山大·法伯

@Daniele。您从哪里获得对destinationViewController的引用?
zulkarnain shah's October

当它工作时,由于从rootViewController到目的地的动画是动画的,因此看起来很奇怪,因此故事流程中断了。
泰迪

59

为了扩展上面的各种讨论,这是我的解决方案。具有以下优点:

  • 可以在视图堆栈中的任何地方工作,而不仅限于顶视图(不确定是否确实需要触发,甚至在技术上也可以触发,但是嘿,它就在那里)。
  • 在显示替换项之前,它不会导致弹出OR过渡到前一个视图控制器,它只是显示具有自然过渡的新控制器,后退导航与源控制器的后退导航相同。

密码:

- (void)perform {
    // Grab Variables for readability
    UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
    UIViewController *destinationController = (UIViewController*)[self destinationViewController];
    UINavigationController *navigationController = sourceViewController.navigationController;

    // Get a changeable copy of the stack
    NSMutableArray *controllerStack = [NSMutableArray arrayWithArray:navigationController.viewControllers];
    // Replace the source controller with the destination controller, wherever the source may be
    [controllerStack replaceObjectAtIndex:[controllerStack indexOfObject:sourceViewController] withObject:destinationController];

    // Assign the updated stack with animation
    [navigationController setViewControllers:controllerStack animated:YES];
}

真好!如果您正在执行“上一个”场景而不是“下一个”场景,那么弹出过渡(或完全滑动到正确的效果)如何?
finneycanhelp 2015年

不确定我是否真的知道您的要求,但是您可以修改堆栈以使用所需的任何内容插入/替换上一个视图,然后只需关闭顶视图即可触发正常过渡...
ima747

26

自定义segue对我不起作用,因为我有一个Splash视图控制器,我想替换它。由于列表中只有一个视图控制器,因此popToRootViewController仍将Splash留在堆栈上。我使用以下代码替换了单个控制器

-(void)perform {
    UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
    UIViewController *destinationController = (UIViewController*)[self destinationViewController];
    UINavigationController *navigationController = sourceViewController.navigationController;
    [navigationController setViewControllers:@[destinationController] animated:YES];
}

现在在Swift 4中:

class ReplaceSegue: UIStoryboardSegue {

    override func perform() {
        source.navigationController?.setViewControllers([destination], animated: true)
    }
}

现在在Swift 2.0中

class ReplaceSegue: UIStoryboardSegue {

    override func perform() {
        sourceViewController.navigationController?.setViewControllers([destinationViewController], animated: true)
    }
}

我有一个汉堡菜单作为navigationViewController。您的代码可以工作,但它代替了我在菜单中使用的tableView而不是viewController(根)。
PaulBénéteau'04

有趣的@greenpoisononeTV您在调用哪个控制器的“ performSegue”?由于sourceViewController和目标控制器是通过segue设置的。
christophercotton

我创建了CTRL +拖动赛格瑞从我的一个tableViewCellviewController。segue设置为ReplaceSegue该类的自定义设置。
PaulBénéteau17年

18

迅速的ima747 2版本答案:

override func perform() {
    let navigationController: UINavigationController = sourceViewController.navigationController!;

    var controllerStack = navigationController.viewControllers;
    let index = controllerStack.indexOf(sourceViewController);
    controllerStack[index!] = destinationViewController

    navigationController.setViewControllers(controllerStack, animated: true);
}

正如他提到的那样,它具有以下优点:

  • 可以在视图堆栈中的任何地方工作,而不仅限于顶视图(不确定是否确实需要触发,甚至在技术上也可以触发,但是嘿,它就在那里)。
  • 在显示替换项之前,它不会导致弹出OR过渡到前一个视图控制器,它只是显示具有自然过渡的新控制器,后退导航与源控制器的后退导航相同。

6
为了使其更安全,您可以if let有条件地获取索引,这样就无需强行打开变量包装,而该变量可能容易出错。
阿方索

那么,什么时候使用该实现从堆栈中弹出sourceViewController呢?
zulkarnain shah

您还从哪里获得对destinationViewController的引用?
zulkarnain shah

17

对于这个问题,我认为答案很简单

  1. 从NavigationController获取视图控制器数组
  2. 删除最后一个ViewController(当前视图控制器)
  3. 最后插入一个新的
  4. 然后将ViewControllers的数组设置为下面的navigationController:

     if let navController = self.navigationController {
        let newVC = DestinationViewController(nibName: "DestinationViewController", bundle: nil)
    
        var stack = navController.viewControllers
        stack.remove(at: stack.count - 1)       // remove current VC
        stack.insert(newVC, at: stack.count) // add the new one
        navController.setViewControllers(stack, animated: true) // boom!
     }
    

Swift 3完美搭配

希望它对一些新手有所帮助。

干杯。


什么是DestinationViewController?
PaulBénéteau'04

这是您要导航到的视图控制器。
拉梅斯瓦尔·普拉萨德

无法用该代码加载我的目标VC。崩溃。您将此代码添加到哪种方法?
zulkarnain shah

9

使用unsegue segue是解决此问题的最合适方法。我同意劳罗。

这是对从detailsViewController [或viewController3]到myAuthViewController [或viewController1]的展开设置的简要说明。

本质上,这就是您要通过代码执行放松搜索的方式。

  • 在要展开的viewController中实现IBAction方法(在本例中为viewController1)。方法名称可以是任何东西,只要它采用一个UIStoryboardSegue类型的参数即可。

    @IBAction func unwindToMyAuth(segue: UIStoryboardSegue) {
    println("segue with ID: %@", segue.Identifier)
    }
    
  • 在要从中展开的viewController(3)中链接此方法。要进行链接,请在viewController顶部的退出图标上单击鼠标右键(双击),此时“ unwindToMyAuth”方法将显示在弹出框中。控制从此方法单击第一个图标,即viewController图标(也位于viewController顶部,与退出图标在同一行)。选择弹出的“手动”选项。

  • 在“文档”大纲中,对于同一视图(viewController3),选择刚创建的展开序列。转到属性检查器,并为此展开任务分配唯一的标识符。现在,我们可以使用通用的解散功能。

  • 现在,展开序列可以像代码中的任何其他序列一样执行。

    performSegueWithIdentifier("unwind.to.myauth", sender: nil)
    

这种方法将使您从viewController3转到viewController1,而无需从导航层次结构中删除viewController2。

与其他命令不同,展开命令不会实例化视图控制器,它们只会转到导航层次结构中的现有视图控制器。


5

正如前面的答案中提到的,弹出不动画,然后再推送动画看起来不太好,因为用户会看到实际的过程。我建议您先推动动画,然后删除以前的vc。像这样:

extension UINavigationController {
    func replaceCurrentViewController(with viewController: UIViewController, animated: Bool) {
        pushViewController(viewController, animated: animated)
        let indexToRemove = viewControllers.count - 2
        if indexToRemove >= 0 {
            viewControllers.remove(at: indexToRemove)
        }
    }
}

1
感谢您的回答,它就像一个魅力。我喜欢扩展的想法
Murat Akdeniz

5

这是一种快速的Swift 4/5解决方案,方法是创建一个自定义序列,用新的(不带动画)替换(顶部堆栈)viewcontroller:

class SegueNavigationReplaceTop: UIStoryboardSegue {

    override func perform () {
        guard let navigationController = source.navigationController else { return }
        navigationController.popViewController(animated: false)
        navigationController.pushViewController(destination, animated: false)
    }
}

2

使用下面的代码最后一个视图控制器,您可以使用其他按钮或使用您自己的按钮代替我曾经使用过的取消按钮

- (void)viewDidLoad
{
 [super viewDidLoad];

 [self.navigationController setNavigationBarHidden:YES];

 UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(dismiss:)];

self.navigationItemSetting.leftBarButtonItem = cancelButton;

}

- (IBAction)dismissSettings:(id)sender
{

// your logout code for social media selected
[self.navigationController popToRootViewControllerAnimated:YES];

}

你可能是setNavigationBarHidden:NO什么意思?您的代码运行良好,谢谢,它也是社交网络注销代码的好地方。
亚历山大·法伯

2

您真正应该做的是模态地在菜单上方显示一个UINavigationController包含社交网络UIViewControllers的菜单UIViewControllerUINavigationController如果需要,可以将其嵌入到菜单中)。然后,一旦用户通过身份验证,您便关闭了社交网络UINavigationControllerUIViewController再次显示菜单。


2

swift3创建一个segue -add标识符-add并在cocoatouch文件的segue(storyboard)自定义情节提要类中进行设置-在自定义类中覆盖perform()

override func perform() {
    let sourceViewController = self.source
    let destinationController = self.destination
    let navigationController = sourceViewController.navigationController
    // Pop to root view controller (not animated) before pushing
    if self.identifier == "your identifier"{
    navigationController?.popViewController(animated: false)
    navigationController?.pushViewController(destinationController, animated: true)
    }
    }

-您还必须在源ViewController中重写一种方法

  override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
    return false
}

1

好吧,您还可以做的是使用展开视图控制器的东西。

实际上,我认为这正是您所需要的。

检查此条目:Unwind segue的用途是什么,如何使用它们?


1
我不相信您可以通过放松步态来获得期望的行为,因为放松只是为了回头。替换实际上是向后退,然后一步就转发到另一个控制器。
2014年

问题是问“如何直接从视图3返回视图1”而不是再次转发?
zcui93 '16

1

这在Swift 3中对我有用

class ReplaceSegue: UIStoryboardSegue {
    override func perform() {
        if let navVC = source.navigationController {
            navVC.pushViewController(destination, animated: true)
        } else {
            super.perform()
        }
    }
}

1

怎么样:)我现在是个老问题了,但这很有魅力:

UIViewController *destinationController = [[UIViewController alloc] init];
UINavigationController *newNavigation = [[UINavigationController alloc] init];
[newNavigation setViewControllers:@[destinationController]];
[[[UIApplication sharedApplication] delegate] window].rootViewController = newNavigation;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.