I have been working with modal view controllers for a while now but I was wondering whether or not there is a function to call to let the underlying view controller know that a modal view controller that has been overlaying it has been dismissed.
If that is possible, how can you tell in a view controller that a modal view has been dismissed so that e.g. another modal view controller can be opened right after the old one has been dismissed?
Thanks in advance
This can be easily done using delegation.
Create a protocol in the modal view controller class(the view controller that is being presented).
protocol ModalViewControllerDelegate:class {
func dismissed()
}
Also create a delegate property and call the delegate method when the modal view controller is dismissed, say on tap of a button. Here is some code to help you with that.
class ModalViewController1: UIViewController {
var delegate:ModalViewControllerDelegate?
#IBAction func back(_ sender: Any) {
delegate?.dismissed()
}
}
Now in the presenting view controllers's prepare for segue method, make the presenting view controller as the delegate of the presented modal view controller. Heres some code.
import UIKit
class ViewController: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "modal1" {
if let modalVC1 = segue.destination as? ModalViewController1 {
modalVC1.delegate = self
}
}
}
}
extension ViewController: ModalViewControllerDelegate {
func dismissed() {
dismiss(animated: true, completion: nil)//dismiss the presented view controller
//do whatever you want after it is dismissed
//like opening another modal view controller
}
}
The dismissed function will be called after the modal view controller's back button is tapped and you can dismiss the presented view controller and also proceed with whatever you want to do after that.
Related
In iOS 13, viewWillAppear is not called when dismissing view controller. As a workaround, it is mentioned to override UIAdaptivePresentationControllerDelegate delegate, but it's not working for me. What am I doing wrong?
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MyVC" {
let destination = segue.destination as! MyViewController
destination.presentationController?.delegate = self
}
}
And then,
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
resumePipeline() //<--Does not get called
}
What am I doing wrong?
You're probably assuming that presentationControllerDidDismiss is always called when the dismissal takes place. That's a false assumption. It is called when the user drags down on the presented view to dismiss it.
You need to think of the presented view controller as if it were a popover. It isn't completely replacing the presenting view controller's view; it just covers it partially. So there is no viewDidAppear call, because the main view never disappeared.
Either you need to go back to forcing your presented view controller to be fullScreen or you need to adapt your architecture to work with the new style of presented view controller.
I have a view controller with a container view that has a tab bar controller embedded in it. Im using this to display 3 different view controllers based on what is pressed from a segmented control in the main vc. I have been following this solution: https://stackoverflow.com/a/38283669/11536234
My problem is that when I change the segmented control index (by pressing a different segment) I can't figure out how to "reach" the prepare function.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("prepare reached")
//super.prepare(for: segue, sender: sender)
//switch(segue.identifier ?? "") {
//case "TabBar":
guard let TabController = segue.destination as? UITabBarController else {
fatalError("Unexpected destination: \(segue.destination)")
}
TabController.selectedIndex = toggle.selectedSegmentIndex
//default:
//fatalError("Unexpected Segue Identifier; \(segue.identifier)")
//}
}
#IBAction func toggleAction(_ sender: UISegmentedControl) {
print("toggle is now at index: ", toggle.selectedSegmentIndex)
//performSegue(withIdentifier: "TabBar", sender: sender)
//container.bringSubview(toFront: views[sender.selectedSegmentIndex])
}
So far i have tried placing a performsegue function in an action function linked to the segmented control. This doesn't work, however, because it essentially adds another embedded tab bar programmatically or calls the embed segue again and I receive this error statement: "There are unexpected subviews in the container view. Perhaps the embed segue has already fired once or a subview was added programmatically?"
*The commented lines of code are there to show what I've tried that hasn't worked vs where I'm at.
When you embed a view controller to a container view from another view controller(MainVC), the segue is performed only once when MainVC loads. To pass values to the embedded UIViewController/UITabBarController, you need to get the child view controller and send the data
class MainVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func segmentControlAction(_ sender: UISegmentedControl) {
if let tabBarVC = self.children.first(where: { $0 is UITabBarController }) as? UITabBarController {
tabBarVC.selectedIndex = sender.selectedSegmentIndex
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//called before mainvc viewDidLoad
let destinationVC = segue.destination as? UITabBarController
destinationVC?.selectedIndex = 1
}
}
You can't do what you are trying to do.
With embed segues the segue fires when the host view controller is first loads. That invokes your prepare(for:sender) method, once and only once, before the embedded view controller's views are loaded. It doesn't get called again.
What you need to do is to save a pointer to your child view controller in an instance variable. Define a protocol that the parent uses to talk to the child, and then use that protocol to send a message to the child when the user selects a different segment in your segmented control.
Then the child (presumably the tab bar controller) can switch selected tabs in response to the message you define.
I have a TabBarController in my Main.storyboard file.
In my Upload.storyboard I am presenting a ViewController from the Main.storyboard file, however, it doesn't contain the tab bar.
The viewProfile button should go to a tab called Sharks and within that present a view controller based on data gathered in the Upload.storyboard (modal view).
Can I add the tab bar programmatically or am I not properly presenting the correct VC?
// MARK: - Actions
#IBAction func viewProfileButtonPressed(_ sender: UIButton) {
let stb = UIStoryboard(name: "Main", bundle: nil)
let sharkProfile = stb.instantiateViewController(withIdentifier: "sharkProfile") as! SharkProfileTableViewController
self.present(sharkProfile, animated: true) {
// add tab bar here?
}
}
What you need to do is present the tab bar view controller, not the view controller that is embedded in it
One method is to create a Delegate Protocol to allow a tap on View Profile button to "call back" to the View Controller that presented it. When that callback is received, the VC sets the "current" tab.
It will look something like this:
// define "call back" delegate protocol
protocol EncounterUploadedDelegate : class {
func didTapSharkProfileButton()
}
The Encounter view controller will need to conform to that protocol:
class EncounterViewController: UIViewController, EncounterUploadedDelegate {
// the normal stuff for this VC and all the other code for it
// ...
// conform to the protocol
func didTapSharkProfileButton() -> Void {
// when we get this call-back, switch to the Shark Profile tab
// tabs are zero-based, so assuming "SharkProfile" is
// is the 4th tab...
self.tabBarController?.selectedIndex = 3
}
// assuming a "Show Modal" segue named "ShowEncounterUploadSegue" is used
// to present the Modal View
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowEncounterUploadSegue" {
let vc = segue.destination as! TabModalVC
vc.encounterDelegate = self
}
}
}
The view controller to be presented as modal:
class TabModalVC: UIViewController {
weak var encounterDelegate: EncounterUploadedDelegate?
#IBAction func profileButtonTapped(_ sender: Any) {
// dismiss self (the modal view)
self.dismiss(animated: true, completion: nil)
// this will call back to the delegate, if one has been assigned
encounterDelegate?.didTapSharkProfileButton()
}
}
Hope that all makes sense :)
How would I go about dismissing a view controller once a segue has been performed? Once the new view controller has animated on top, I want to dismiss the view controller underneath (the one which the segue was initially triggered from).
I tried the following code but I am getting issues with views not being in the heirarchy.
#IBAction func gotoSection1(_ sender: AnyObject) {
let presentingViewController: UIViewController! = self.presentingViewController
self.dismiss(animated: false) {
presentingViewController.dismiss(animated: false, completion: nil)
}
}
Any help would be greatly apprceiated.
Try this:
Add the below code to first view controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
self.dismiss(animated: true, completion: nil)
}
It will dismiss the first view controller before presenting second view controller on top of it.
Edit:
Follow these steps and check:
Create a button in 1st View controller
Connect button to 2nd View controller with modal segue
Implement prepareForSegue in 1st view controller
Refer to these links:
Navigation Controller
https://developer.apple.com/reference/uikit/uinavigationcontroller
Presenting a controller
https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/PresentingaViewController.html#//apple_ref/doc/uid/TP40007457-CH14-SW1
I have a few view controllers with segues presenting modally to a menu view controller. I also have a few buttons set up on the menu view. How do I perform a segue between the view controller (that is behind the modal) and a new view controller immediately after pressing a button on the menu and dismissing the modal? Thank you in advanced.
you can use prepareForSegue method between your view controllers
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "yourSegueIdentier" {
let indexPath = self.tableView.indexPathForSelectedRow as NSIndexPath!
//take your actions here
}
}
hope that helped