0

Swift 5, Xcode 12.4

I've got multiple UIViewControllers and when there's an error in the very last one (VC4), then I want to go back to the previous one (VC3) after displaying an error message.

VC3:

public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    displayExtraInfo(indexPath.row)
}
    
private func displayExtraInfo(_ pos:Int) {
    performSegue(withIdentifier: "segue3-4", sender: self)
}

VC4:

override func viewDidLoad() {
    doStuff()
    doMoreStuff()
}

private func doStuff() {
    if someCondition {
        //do stuff
    } else {
        //Error!
        //Display error message, when "okay" button is clicked:
        //TODO: Go back to VC3
    }
}

The layout in storyboard:

NC1 - VC1 - NC2 - VC2 - VC3 - VC4

Clicking a specific navigation bar button in VC2 loads VC3 ("Show (e.g. Push)" segue created in storyboard/XIB). To get to VC4 you have to click on a UITableView item in VC3. In VC3 & VC4 the "<" button in the navigation bar was added automatically and it works.

I tried both solutions suggested in e.g. this answer:

  1. self.navigationController?.popViewController(animated: true)
  2. self.dismiss(animated: true, completion: nil)

navigationController isn't nil but neither version does anything and doMoreStuff() is still called.

How do I go back to VC3 in the above Swift function, while still respecting the stack?

Neph
  • 1,823
  • 2
  • 31
  • 69
  • Use an unwind segue. Why do you have two navigation controllers? – Paulw11 Apr 16 '21 at 11:15
  • @Paulw11 The second one could be presented modally? – Sweeper Apr 16 '21 at 11:18
  • Once you have a navigation controller on the stack there is rarely a good reason to add another one. – Paulw11 Apr 16 '21 at 11:19
  • @Paulw11 VC1 is the login screen and I don't want users to go back to it from VC2 but obviously want proper navigation between VC2, VC3 and VC4 with automatic "back" buttons, that's why I created NC2. There's also a VC1B, which has a couple of settings that have to be changed before logging in, that's why I created NC1 to get the "back" button there. "Unwind" segue? Aren't those created in XIB? The error dialog I want to use is only created at runtime. – Neph Apr 16 '21 at 11:25
  • 1
    I'm not sure you can pop VCs when you are in `viewDidLoad`... – Sweeper Apr 16 '21 at 11:25
  • @Sweeper When can you do that then? VC4 displays an image in a `UIScrollView`. This image is loaded from the "Documents" folder at runtime and if it doesn't exist, I want to display the error dialog, then go back to VC3 because there's no point to display VC4 without the image because everything else heavily relies on it (e.g. other stuff that happens in `viewDidLayoutSubviews`). – Neph Apr 16 '21 at 11:30
  • 1
    Doing it in or after `viewDidAppear` would be a safe choice, but I would suggest that you change your design. Don't you think that an error message that shows up for just a split second, then disappears because you popped the VC is a bit weird? How about popping the VC after the user taps on an OK button or something like that on the error message? – Sweeper Apr 16 '21 at 11:33
  • @Sweeper Yes, I'm currently doing it in `viewDidAppear`, not sure how you would do it afterwards because everything else would need some type of user interaction to be called afaik. My plan is: Display the dialog, then go back to VC3 when the user clicks on "okay". Sorry, bad comment "design", I edited it. Currently I'm just trying to get the "go back to VC3" part to work, without the error dialog. – Neph Apr 16 '21 at 11:37
  • Unwind segues are created in storyboards. You can trigger them with `perform(segueWithIdentifier)`. It might be better to instantiate vc2 from the storyboard when login succeeds and replace the root view controller in NC1 with that new instance. Regardless an unwind segue is what you want if you are using segues to move forward. – Paulw11 Apr 16 '21 at 11:43
  • @Paulw11 Is there no way to call whatever function is called when you use the automatically created "back" button in the navigation bar in VC4? `might be better to instantiate vc2 from the storyboard` - sorry, I'm not sure what you mean. When the login was successful, then I call `performSegue(withIdentifier: "segue1-2", sender: self)` (also regular "show") in VC1 and in `prepare` I do: `segue.destination as? UINavigationController`. – Neph Apr 16 '21 at 11:49
  • The first line will do what the back button does. To get a view controller from the storyboard you can use `let vc = self.storyboard,instantiateViewController(withIdentifier:"vc2")` and then `self.navigationController.viewControllers=[vc]`. The other thing you can do is segue to vc2 as you do already and then in viewDidAppear of vc2 you can remove the first element in the navigation controllers `viewControllers` array to prevent going back to the login screen – Paulw11 Apr 16 '21 at 11:52
  • @Paulw11 `navigationController?.popViewController`? That's exactly what I want: Go back and use the automatic VC stack managment. I looked at "unwind segue" tutorials and they all seem to use it to skip multiple VCs, so in my case e.g. VC4 -> VC2 but none of them uses it to only go back by one, which seems a bit weird tbh. – Neph Apr 16 '21 at 11:58
  • The unwind segue will go back 1 or 2 or 27. They are often used to jump back to a specific point where there may be an unknown number of intermediate view controllers but there is no reason you can't use it to go back 1. I think if you resolve your use of multiple navigation controllers you will find that the popViewController starts working. – Paulw11 Apr 16 '21 at 11:59
  • @Paulw11 I see. What happens to the stack? Is that cleaned automatically, so in my case, is VC4 deleted from it? You said that 1. `popViewController` does what the "back" button does and that's what I want, so why would that not work in my case? My app's almost done, so tbh I'd rather not mess with the overall layout too much, escpecially with the navigation controllers because e.g. NC1 prevents VC1 from switching to landscape orientation (had to put it in the NC, it didn't work in VC1) and if I removed NC2, landscape would probably also be effect in the other VCs. – Neph Apr 16 '21 at 12:05
  • VC4 would be deallocated as long as there are no other references. I think popViewController isn't working because you have two navigation controllers in the stack. – Paulw11 Apr 16 '21 at 12:08
  • @Paulw11 So it won't cause any conflicts when I go from VC4 to VC3 with an unwind segue but then use the "back" button in VC3 to go back to VC2 again? That's what I meant. Would it be possible to delete VC1 and NC2 from the stack in VC2 but without changing the actual layout in storyboard? – Neph Apr 16 '21 at 12:16
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/231213/discussion-between-paulw11-and-neph). – Paulw11 Apr 16 '21 at 12:54
  • @Paulw11 Sorry for the delayed reply. I can't get rid of NC2 because that'll also prevent landscape orientation in VC2,... – Neph Apr 19 '21 at 10:44
  • @Sweeper Sorry for the delayed reply. Another sorry, I mixed up the functions. The check for `someCondition` happens in `viewDidLoad`, I also tried to add the dialog there but it didn't even display it, so it looks like you're right. I added an "error" `Bool` and check it before I call `doMoreStuff()` in `viewDidLoad` and also before I do stuff in `viewDidLayoutSubviews`. If I then open the error dialog in `viewDidAppear`, it actually shows up and also switches back to VC3 when I click on the "Okay" button, using `popViewController`. `dismiss` goes back to VC1 instead. – Neph Apr 19 '21 at 11:55
  • That doesn't make any sense. There is nothing you can do with nc2 that you can't also do with nc1 – Paulw11 Apr 19 '21 at 12:25
  • @Paulw11 I want to limit VC1 (+VC1B) to only use portrait orientation. I tried to add the `supportedInterfaceOrientations` override to the VC1 class directly but it didn't work, probably because of the NC. I then created a NC1 class and added it there and it instantly worked. VC2,... can use whatever orientation they want (there's no extra NC2 class setting anything) but if I were to get rid of NC2, the orientation override in NC1 would also affect VC2,..., so as a global app limitation. – Neph Apr 19 '21 at 12:50
  • You can simply change the value returned by supported orientations after login. Or replace the root view controller for your window with nc2. Having a navigation controller in a navigation controller is just messy. – Paulw11 Apr 19 '21 at 12:56
  • @Paulw11 That would have to be a different var then because I can't access `supportedInterfaceOrientations` from a different class. This doesn't solve one problem though: I don't want users to go back to VC1 from VC2. Why is it a problem if there are multiple NCs (google doesn't show any clear answers)? Changing everything and testing it would take me multiple hours and tbh, I can't see why that should be necessary because the app is working fine, there are no bugs, no problems with segues (the current one was caused by trying to do stuff too early), no errors in XIB,.... – Neph Apr 19 '21 at 13:09
  • @Sweeper Want to post your comment as an answer? – Neph Apr 28 '21 at 09:33

0 Answers0