1

I have a series of three view controllers, the first two of which are wrapped in a UINavigationController, and are managed by the default Main.storyboard file. Let's call them A and B, respectively. I have more view controllers throughout the file, each instantiated and managed separately, though I am working on moving them into all-code solutions. The aforementioned third view controller I have already in code (C) is one that at various times is instantiated and presented by B.

A and B have a normal segue relationship setup in Interface Builder and called via code. I do the same with C from B, via code (instantiate and presentViewController(_:)). There are a few cases where an action in C invalidates B's raison d'être, and thus I must dismiss both.

So far, I have been calling dismissViewControllerAnimated(_:) from C, and then checking in B's viewDidAppear(_:) whether it should be dismissed, and dismissing it the same way if so. During this process, the user is thrown back though the VC hierarchy, watching as empty views fly back to whence they came, leaving time for them to experiment with controls that no longer work, and may crash the app. This is a rather disconcerting user experience that I wish to do away with. I could simply disable everything, but that's a lot of controls that I'd rather not mess with if I can avoid it...

I understand that IB supports "unwind segues," which can dismiss an entire VC hierarchy simultaneously, though those only seem to handle view controllers in the storyboard. Is there any way to get this behavior (dismiss everything to A) programmatically, without having to revert much of the work I've already done, considering that part of my hierarchy is contained in a UINavigationController?


UPDATE:

So, I got the dismissal to work properly, by passing a reference to the presenting view, and calling its dismiss segue before leaving. That approach came with its own issues and navigation controller weirdness, but with some tweaking it might be usable.

Honestly, it would be much easier to remove the feature entirely at this point, as it's mostly a convenience.

For the sake of science, I'm going to keep at it until I decide ether way, and answer back here for anyone googling this way.


UPDATE:

Ew... It seems this code was older than I thought. I actually have two navigation controllers, to support a custom modal animation into and out of B, with a custom unwind segue there (there you go). In order to get the animation I want, I may as well toss C into the storyboard and make a custom unwind segue.

If I don't care about animation, simply disabling animation on the custom unwind got both B and C to vanish promptly, and together. Trouble is, it's a bit jolting for my taste...

vacawama's suggestion actually makes a lot of sense, and serves me right for not checking the documentation! UIViewController already keeps a reference to its presentingViewController. Going back by two is a pinch, simply climbing back that hierarchy and dismissing the one I want. Works like a charm, without animation doesn't happen at all...

Gonna post an answer here soon with what I've found thus far.

Community
  • 1
  • 1
AverageHelper
  • 2,144
  • 2
  • 22
  • 34
  • 2
    You can't use segues, normal or unwind, without a storyboard. Since you have a navigation controller, you can just manipulate its `viewControllers` property directly – Paulw11 Jul 25 '16 at 20:51
  • "though I am working on moving them into all-code solutions" ... Why are you doing that? It just makes the project harder to maintain and you're doing more work than you have to. – Rob Jul 25 '16 at 20:57
  • @Paulw11 That's something I've considered, but haven't had a chance to try myself yet... Would I just pass in a reference to the nav controller when I present `C`? I'll try it now and get back with what I find. – AverageHelper Jul 25 '16 at 21:02
  • You can access the navigationController from C's `navigationController` property. When you determine that you need to go from C back to A, simply get this property, create an array that only has A in it (ie the first element in the navigationController's `viewControllers` property) and the call `setViewControllers:animated:` – Paulw11 Jul 25 '16 at 21:05
  • @Paulw11 So... `navigationController?.viewControllers.first` returned `self`. Debugger tells me `navigationController?.viewControllers` only has `self` in it. So no go. Might have something to do with the fact that `C` is presented modally, and therefore sits outside of the main nav controller's hierarchy... – AverageHelper Jul 25 '16 at 21:14
  • Yeah, I just realised that you are presenting C from B, so it is modal, not a navigation controller push. You can still set a reference to the navigation controller in C from B when you present it and then C can manipulate the array before it dismisses itself. Alternatively, present C on the navigation controller rather than modally and hide the back button/navigation bar if appropriate. – Paulw11 Jul 25 '16 at 21:18
  • @Rob Got a point there. I may need to reconsider my approach. Been on the fence about storyboards for a while... heh. This is getting so complicated I'm considering removing the feature entirely. Isn't all that important anyway... lol – AverageHelper Jul 25 '16 at 21:50
  • Take a look at this answer. It is almost exactly what you're trying to do. http://stackoverflow.com/a/38163792/1630618 – vacawama Jul 25 '16 at 22:00
  • @vacawama As with FullMetalFist's answer, the VC I'm trying to dismiss has a separate navigation controller, and only contains that first controller. Nothing else to pop to. :) – AverageHelper Jul 25 '16 at 22:04
  • 1
    If they are both presented modally, then you could use `self.presentingViewController?.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)` to go back two view controllers. – vacawama Jul 25 '16 at 22:10

2 Answers2

1

So, science prevails, and a solution is found.

It's really a pain to do if you don't like writing custom segues and animators. Gonna do that eventually, but for now it's more profitable to just not enable the feature at all. (Thank goodness it's easy to toggle on my end).

I found that running my dismiss segue (I use a custom one) on B, then dismissing C did the trick, so long as I didn't use animations for either. Nothing unexpected there at all!

Further, I could get the same effect in one line (animation doesn't matter, so neither does custom segue) by running:

presentingViewController?.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)

from C's equivalent of goAllTheWayBack().

Gross, but it got the job done. A bit more tweaking, like actually using the storyboard (ugh) for its unwind segue and writing a custom animator, it'd look fancier than a pig in a blanket!

I'd about declare this horse good and dead. Thanks all!

AverageHelper
  • 2,144
  • 2
  • 22
  • 34
0

Have you tried Atomic Object's approach

TL;DR

use IB and ctrl-drag 'C' view controller (yellow) to 'Exit',

select 'prepareForUnwind',

give the unwind segue an identifier,

then you can perform the unwind programmatically and it will skip 'B' view controller.

FullMetalFist
  • 208
  • 1
  • 2
  • 14
  • As stated above, I'm trying to keep `C` out of the storyboard for now. So no, I haven't tried making an unwind segue for it in IB. – AverageHelper Jul 25 '16 at 21:04
  • I misread that, and apologize for my mistake. I think segues are only available with IB: http://stackoverflow.com/questions/9674685/creating-a-segue-programmatically – FullMetalFist Jul 25 '16 at 21:11
  • Makes sense, and no worries. Wonder if there's a good way to just dismiss all the things... – AverageHelper Jul 25 '16 at 21:15