I have a basic scenario:
I present a VC modally using self.present(, animated:, completion:).
Sometimes due to interactions in this modal VC i need to close one modal and open another one.
So i do the following:
weak var presenter = self.presentingViewController
let newVc = UIViewController()
presenter?.dismiss(animated: true, completion: {
presenter?.present(newVc, animated: true, completion: nil)
})
This works but there is the annoying delay when switching the VC's when user sees the original presenter and can try to interact with it (to open other modals...).
I tried setting animated: false but that doesn't seem to work :/
I can't really switch to UINavigationController model for this because the modals i am presenting themselves are Page View Controllers and have the whole hierarchy of dependent views; the user is never going 'back'; so i'd really like to just present the new modal as quickly as possible...
Update My question is not about how to control or choose the animation. My questions is about having no delay between the modals.
The built-in view controller architecture that switches views with no transition is the tab bar controller. So just turn your view controller into a tab bar controller — with no visible tab bar! To change to the other view controller just change tabs (in code). The change is instant.
This screencast makes it clear that this works as described. We present a view controller (yellow). Then we switch back and forth between two view controllers (green and yellow) as the presented view controllers, instantly. Finally, we dismiss whichever one (green or yellow) is showing. I'm doing it all with simple buttons but that's just for the demo; obviously you could do this however you like. It's the architecture that's the important thing.
I can think only of solutions which would require you to handle the animations yourself
create custom modal transition using UIViewControllerTransitionCoordinator
add your controllers to container views as suggested by #muhammed-gül
present newVC over self and dismiss all presented controllers when you're done
And just a tip, you don't always need to wait for the dismiss completion closure, you can call this and it usually works, but still the underlying viewController is visible.
dismiss(animated: true)
present(newVC, animated: true)
Related
I am new to swift and I am curious about the best way to move from one view to another. I have a main view that is a tab on a tab bar and embedded in a navigation controller. From this main view, I am taking user content and then displaying that content on the main view. Currently I am segueing from the main view to a view that collects content and then another segue back to the main view. The only problem is that when I do this I get a back button and even if I hide the back button, I am just continuing to put screens on top of the tab. What would be the best way to transition from the view that collects content back to the main view?
Basically there're 2 general ways how view controllers are shown and hidden:
Push in the navigation controller (either with 'Show' segue or in code self.navigationController?.pushViewController(viewController, animated: true). In that case the normal way to return to previous screen would be to use 'Back' navigation button. If you need to do it manually you can use self.navigationController?.popViewController(animated: true) inside your view controller or use unwind segue (though I rarely see it used). If you need to come back to the very first screen in your navigation stack you can use self.navigationController?.popToRootViewController(animated: false).
Modal presentation (either with 'Present Modally' segue or with self.present(viewController, animated: false). In that case in order to return to the previous view you have to add some close/cancel button on your screen and bind it with the code which call self.dismiss(animated: true, completion: nil).
Anyway to have a full picture I'd recommend to get familiar with Apple guide on this subject.
And if you need to pass some data between your controllers, ther're various ways to do it, like assigning it directly to a property in prepare(for segue: UIStoryboardSegue, sender: Any?), implementing delegate/callback interactions, implementing observers, shared back storages, etc.
I've had this issue for months with multiple views, both Apple provided like ImagePicker and VCs from storyboard.
I believe that it has something to do with the underlying views we have both a tab bar controller and navigation controller in most views.
Strange thing is using some open source views from pods does not cause this bug.
So I'm two views deep on a navigation controller and present another view modally on top with present(vc, animated: true, completion: {})
Works like a charm, now dismissing that view with dismiss(animated: true, completion: nil) throws me back all the way to the initial view or root view of the navigation controller, had both happen before, depending on the presented view.
Update:
Build a sample project trying to reproduce the behavior but failed. Drew a reduced diagram to better explain the current bug behavior.
Also noticed that if I'm invoking the post view one step earlier in the Fandom view it works as expected.
In my case i am using UITabBarController, and I wrote code in viewWillAppear of UITabBarController
self.selectedIndex = 2
so when i present any thing from any controller whose parent is UITabBarController and when i dismiss that it automatically open third tab of UITabBarController.
Maybe you explicitly wrote any code to select specific index of TabBar.
Maybe this is useful for you or anyone else.
I am working on some old code that I didn't write, and it's really not architected well...
The situation is that a view controller presents a custom view controller modally, however every 30 seconds the presenting view controller is recreated.
The issue here is that if the modal is on screen when this happens, then any effort to dismiss it results in odd behaviour (such as a white screen).
I have tried calling [self.presentedViewController dismissViewControllerAnimated]; on the newly recreated controller, but presentedViewController is nil as you would expect.
I have also tried keeping a weak reference to the modal view controller, then when the presenting VC is reloaded, setting this value to that of the old VC. This has allowed me to call self.customModalVC dismissViewControllerAnimated]; but this is causing the aforementioned white screen, perhaps because it's presenting VC is no longer in the stack?
Any and all suggestions appreciated.
Try passing the navigation controller to newly presented ViewController:
presentedVC.navigation = self.navigationController
Add this to newly created one for dismissing
self.dismiss(animated: false) {
_ = self.navigation?.popViewController(animated: true)
}
When I try presenting the modal controller right from the table view controller (could be a normal view controller as well), it appears behind the tab bar and looks quite ugly as I'm using a blur effect on it. I am using a navigation controller because I need to have a bar at the top and, after research, found that's the best way to do it.
I have found that doing something like:
self.parent?.parent?.present(ModelViewController(), animated: true, completion: nil)
when wanting to present the modal controller works. However, I imagine this isn't very safe. What is the correct way of doing this?
In order for the ModalViewController to present in front of the tab bar, its modalPresentationStyle has to be set to overFullScreen. So for instance when initializing the ModalViewController:
self.modalPresentationStyle = .overFullScreen (Swift 3 syntax)
I have a UIImagePickerController presented on screen. When I choose the photo I need to dismiss the PickerController then immediately present another PhotoEditController. Here is my code
picker.dismissViewControllerAnimated(false, completion: {
self.presentViewController(editPhotoVC, animated: false, completion: nil)
})
There is a 0.1s flash between dismissing old VC and presenting new VC so the presentingViewController (self) is shown. How do I avoid that in an elegant solution not hacking it through? Thanks
The standard way of implementation is to dismiss first view controller (VC) with animation and present second VC with animation.
However, depending on your view hierarchy, you could also have your second VC loaded first and then present first VC on top of it. With this simply dismissing first VC without animation should show underneath second VC without delay.
Third approach, as LLooggaann suggested, don't dismiss first VC and simply present second VC. Once done, dismiss the entire view controller hierarchy in one shot.
You could instantiate PhotoEditController's instance, then do something like this:
instance.willMoveToParentViewController(controllerThatPresentsPicker)
controllerThatPresentsPicker.addChildViewController(instance)
controllerThatPresentsPicker.view.insertSubview(instance.view, belowSubview: picker.view)
instance.didMoveToParentViewController(controllerThatPresentsPicker)
It looks a bit like a hack though
That is most probably because dismiss is sent to the VC that presented the picker and it dismisses the picker modal vc and executes the completion handler after attempting to present the parent VC which is itself.
one solution would be creating your own transition class, you can basically copy paste the code from here to do that