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

时间:2014-01-28 19:19:08

标签: ios iphone objective-c segue uistoryboardsegue

我有a small iPhone app,它使用导航控制器显示3个视图(此处为fullscreen):

Xcode screenshot

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

list screenshot

然后它会显示一个OAuth对话框,询问凭据:

login screenshot

并且(之后,在相同的UIWebView中)获取权限:

permissions screenshot

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

details screenshot

这一切都运作良好,但我有一个问题,当用户想要返回并选择另一个社交网络时:

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

我可以在这做什么? Xcode 5.0.2显示了一个非常有限的segues选择 - 推送模态(我无法使用,因为它模糊了我游戏所需的导航栏)和定制

我是一名iOS编程新手,但早些时候我开发了一个Adobe AIR mobile app,可以1)替换视图而不是推送,2)从导航堆栈中删除不需要的视图。

如何在原生应用中做同样的事情?

14 个答案:

答案 0 :(得分:56)

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

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

Segue Code:

- (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];
}

答案 1 :(得分:37)

您可以使用自定义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)

答案 2 :(得分:22)

自定义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)
    }
}

答案 3 :(得分:17)

快速2版的ima747回答:

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转换到前一个视图控制器,它只显示具有自然转换的新控制器,后向导航到源控制器的相同后退导航。

答案 4 :(得分:15)

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

  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!
     }
    
  5. Swift 3完美配合。

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

    干杯。

答案 5 :(得分:9)

使用展开segue将是解决此问题的最佳方法。我同意劳罗。

以下是从detailsViewController [或viewController3]到myAuthViewController [或viewController1]

设置展开segue的简要说明

这实际上就是你如何通过代码执行unwind segue。

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

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

  • 在文档大纲中,对于同一视图(viewController3),选择刚刚创建的展开segue。转到属性检查器并为此展开segue分配唯一标识符。我们现在有一个通用的展开segue准备使用。

  • 现在,unwind segue可以像代码中的任何其他segue一样执行。

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

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

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

答案 6 :(得分:4)

如前所述,流行音乐不是动画,然后推动动画片看起来非常好,因为用户会看到实际的过程。 我建议你先推动动画,然后删除以前的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)
        }
    }
}

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

}

答案 8 :(得分:2)

你真正应该做的是以模态方式呈现UINavigationController包含社交网络UIViewControllers覆盖你的菜单UIViewController(如果你想要的话可​​以嵌入UINavigationController )。然后,一旦用户通过身份验证,您就会关闭社交网络UINavigationController,再次显示您的菜单UIViewController

答案 9 :(得分: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)
    }
    }

- 您还必须覆盖源视图控制器中的一个方法

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

答案 10 :(得分:1)

嗯,你还可以做的是使用unwind视图控制器。

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

请检查此条目:What are Unwind segues for and how do you use them?

答案 11 :(得分:1)

这在Swift 3中对我有用:

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

答案 12 :(得分:1)

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

UIViewController *destinationController = [[UIViewController alloc] init];
UINavigationController *newNavigation = [[UINavigationController alloc] init];
[newNavigation setViewControllers:@[destinationController]];
[[[UIApplication sharedApplication] delegate] window].rootViewController = newNavigation;

答案 13 :(得分:1)

这里有一个快速的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)
    }
}