您如何有条件地修改 UINavigationController 的后退堆栈以跳过以前的条目,而不管您来自哪里?

How do you conditionally modify a UINavigationController's back-stack to skip previous entries regardless of where you came from?

我们正在尝试弄清楚如何在向后导航时有条件地排除后台堆栈中的某些屏幕。例如,采取以下屏幕:

  1. 主仪表板(您可以从此处转到 2 或 3)
  2. 订单管理(您可以从这里转到 3 或 6)
  3. 输入订单
  4. 审核订单
  5. 订单确认(如果您在这里下单,后面应该跳过 3 & 4)
  6. 订单状态(如果您在这里下单,返回应该跳过 5(它已经跳过 3 和 4))

屏幕 3-5 表示输入订单,因此如果您已进入确认页面,我们不希望您返回审查或编辑页面。

出于同样的原因,如果您从确认页面导航至 'Order Status',我们不希望您导航回 'Order Confirmation',而是返回 'Order Management' ] 或 'Main Dashboard'(取决于您是如何到达那里的)

Note: We're aware of popToViewController and how it lets you skip over all controllers between where you are and the one you specify. The issue is I don't know what that view controller is. I only know which I want to skip over (if they are present.) More importantly, the back button reflects what's in the stack, not what I'm popping to. That's why I'm hoping to tell the system 'Just ignore these in the stack when going back'. Hope that makes sense.

我粗略的尝试是像这样修改返回堆栈...

func showConfirmation() {

    guard navigationController = navigationController else {
        return
    }

    navigationController.pushViewController(orderConfirmation, animated:true)

    if let orderEntryIndex  = navigationController.viewControllers.index(of:orderEntry),
       let orderReviewIndex = navigationController.viewControllers.index(of:orderReview){

        navigationController.viewControllers.removeSubrange(orderEntryIndex...orderReviewIndex)
    }
}

func showStatus() {

    guard navigationController = navigationController else {
        return
    }

    navigationController.pushViewController(orderStatus, animated:true)

    if let orderConfirmationIndex = navigationController.viewControllers.index(of:orderConfirmation){

        navigationController.viewControllers.remove(at: orderConfirmationIndex)
    }
}

Note: This is simplified code. We don't actually hang onto instances of the view controllers. We find the indexes by looking for their type in the back-stack. This is for simple illustrative purposes only.

虽然导航有效,但在动画期间,后退按钮显示原始后退位置,然后在动画结束后,标签的值 'snaps' 显示新的后退位置。这当然证明这不是正确的方法。

我知道有 unwind segues,但是担心你要去哪里。我们只想在导航离开后从积压中删除项目,否则保持后退堆栈完好无损。

那么如何实现这种忽略先前步骤的行为呢?

根据您的情况,您可以检查导航堆栈中的视图控制器列表并弹出您想要的视图控制器

    for controller in (self.navigationController?.viewControllers)! {
        if controller is SomeClass {
            self.navigationController?.popToViewController(controller, animated: true)
        }
    }

好的,我知道怎么做了,并且在 UINavigationController 上添加了一个扩展以在将来帮助解决这个问题。

秘诀是使用setViewControllers方法替换整组UIViewController对象,删除你想跳过的对象。为了方便这一点,我传入了一个 UIViewController.Type 数组,我用它来过滤掉我不想要的现有控制器。

extension UINavigationController {

    func pushViewController(_ viewController:UIViewController, removingBackItemsOfType backItemTypesToRemove:[UIViewController.Type], animated:Bool) {

        var viewControllers = self.viewControllers
            .filter{ vc in !backItemTypesToRemove.contains(where:{ vcType in vcType == type(of:vc) } ) }

        viewControllers.append(viewController)

        setViewControllers(viewControllers, animated: animated)
    }
}

所以现在,当我在 'Review Order' 上时,我使用此方法推送到 'Order Confirmation',传递我想从 back-stack 中删除的项目,就像这样。 ..

let orderConfirmation = OrderConfirmation()

let typesToRemove:[UIViewController.Type] = [
    EnterOrder.self,
    ReviewOrder.self
]

navigationController?.pushViewController(orderConfirmation, removingBackItemsOfType:typesToRemove, animated:true)

结果是它推送到新的 Order Confirmation 视图控制器,但是无论我来自哪里,按下后退都会将我带到输入订单之前所在的任何屏幕。

然后我在从订单确认推送到订单状态时执行相同的操作,这次删除 [OrderConfirmation.self]。同样的事情......现在回到让我进入订单输入的屏幕。

简而言之,这使您可以设置我上面描述的场景,而不必担心之前发生的事情。就是你想跳过的。

希望对您有所帮助!