Xcode: How to know the viewController that has presented the current view? - ios

I have two viewControllers "FirstViewController"&"SecondViewControler" that contain a button that presents a new viewController called "BibliothequesViewController" when a button is clicked:
#IBAction func onLibrariesButtonClicked(_ sender: Any) {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc: BibliothequesViewController = storyboard.instantiateViewController(withIdentifier: "BibliothequesViewController") as! BibliothequesViewController
self.present(vc, animated: true, completion: nil)
}
For the time being, the BibliothequesViewController contains a button that's redirects to the FirstViewController with a Segue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("SegWay being performed")
// Every segway has a beginning point and ending point
// We're trying to access the ending point
// as type FullScreenMovieController
// Then we're setting the chosenMovie variable of FullScreenMovieController to 1
let vc = segue.destination as! FirstViewController
vc.receivedSelectedLibraryFromBibliothequesList = self.selectedLibrary
print("vc.receivedSelectedLibraryFromBibliothequesList :", vc.receivedSelectedLibraryFromBibliothequesList )
}
This is the code that performs the Segue:
self.performSegue(withIdentifier: "BiblioToLoginSegue", sender: self)
What I need to implement is:
Redirect to the view (FirstViewController or SecondViewControler) that actually presented BibliothequesViewController.
The code above automatically redirects to FirstViewController whether it was the one that presented BibliothequesViewController or not.
I don't see which approach should I follow to implement this.
Should I use push instead of present in the FirstViewController&SecondViewControler?

The simplest way to teach a class to know something is to add a property:
class BibliothequesViewController {
...
var presentedBy: UIViewController?
...
}
to assign the property after the controller instantiation
let vc: BibliothequesViewController = storyboard.instantiateViewController(withIdentifier: "BibliothequesViewController") as! BibliothequesViewController
vc.presentedBy = self
and to use this property when the time comes.

I took #Roman's advice but I changed a couple of things.
CAVEAT: I am not sure this is the right approach but it worked for me.
BibliothequesViewController
class BibliothequesViewController: UIViewController {
static let sharedInstance = BibliothequesViewController()
var presentedBy: UIViewController?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if BibliothequesViewController.sharedInstance.presentedBy! is LoginViewController {
let vc = segue.destination as! LoginViewController
vc.receivedSelectedLibraryFromBibliothequesList = self.selectedLibrary
} else {
let vc = segue.destination as! ForgetPasswordViewController
vc.receivedSelectedLibraryFromBibliothequesList = self.selectedLibrary
}
}
#IBAction func onLibrarySelected(_ sender: Any) {
if BibliothequesViewController.sharedInstance.presentedBy! is LoginViewController {
self.performSegue(withIdentifier: "BiblioToLoginSegue", sender: self)
} else {
self.performSegue(withIdentifier: "biblioToPasswordForgottenSigue", sender: self)
}
}
}
LoginViewController
class LoginViewController: UIViewController {
#IBAction func onLibrariesButtonClicked(_ sender: Any) {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc: BibliothequesViewController = storyboard.instantiateViewController(withIdentifier: "BibliothequesViewController") as! BibliothequesViewController
BibliothequesViewController.sharedInstance.presentedBy=self
self.present(vc, animated: true, completion: nil)
}
}
ForgetPasswordViewController
class ForgetPasswordViewController: UIViewController {
#IBAction func onLibrariesButtonClicked(_ sender: Any) {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc: BibliothequesViewController = storyboard.instantiateViewController(withIdentifier: "BibliothequesViewController") as! BibliothequesViewController
BibliothequesViewController.sharedInstance.presentedBy=self
self.present(vc, animated: true, completion: nil)
}
}
NOTE: You have to create two Segues from BibliothequesViewController to LoginViewController and ForgetPasswordViewController with the identifiers mentioned in BibliothequesViewController.

Related

NotificationCenter is not working first time when it is posted from viewcontroller to another ViewController

I have 2 ViewControllers called ViewController1 and ViewController2.
I am posting notification from ViewController1 to ViewController2, but at first time when it is posting, at first time NotificationCenter is not working.
When I came back from ViewController2 to ViewController1 and then again if try to move ViewController2 then that time NotificationCenter it is working,
// ViewController1.swift
import UIKit
class ViewController1: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
//Move to next VC
#IBAction func nextButtonClicked(_ sender: Any) {
NotificationCenter.default.post(name: Notification.Name("callMethodPrint1FromVC2"), object: nil)
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let vc2 = storyBoard.instantiateViewController(withIdentifier: "ViewController2Id") as? ViewController2
navigationController?.pushViewController(vc2!, animated: true)
}
}
// ViewController2.swift
import UIKit
class ViewController2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
//Recieve notification
NotificationCenter.default.addObserver(self, selector: #selector(self.print1Method(notification:)), name: Notification.Name("callMethodPrint1FromVC2"), object: nil)
}
#objc func print1Method(notification: Notification) {
print("Notification came from VC 1")
}
#IBAction func backToVC1(_ sender: Any) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let vc2 = storyBoard.instantiateViewController(withIdentifier: "ViewController1Id") as? ViewController1
navigationController?.pushViewController(vc2!, animated: true)
}
//Remove notification object
deinit {
NotificationCenter.default.removeObserver(self, name: Notification.Name("callMethodPrint1FromVC2"), object: nil)
}
}
Actual Output:
When I move ViewController1 to ViewController2 at first time and every time, print1Method method from ViewController2 has to call.
But it is not working as expeted.
Is there Anu issues in my code or I am missing something?
It wont call because ViewController2 is not loaded, and It will call for the second time because you are doing Push instead of Pop while moving backwards which in turns remains the ViewController1 in the memory.
Instead remove notifications and call print1Method from ViewController2 Viewdidload/ViewWillappear/Viewdidappear based on your needs. keep some public variable in ViewController2 and assign the value in prepareforsegue method in ViewController1 if you want to pass some data from ViewController1 to ViewController2
and replace your:
#IBAction func backToVC1(_ sender: Any) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let vc2 = storyBoard.instantiateViewController(withIdentifier: "ViewController1Id") as? ViewController1
navigationController?.pushViewController(vc2!, animated: true)
}
to
#IBAction func backToVC1(_ sender: Any) {
navigationController?.popViewController(animated: true)
}
You can do this by adding just one step.
What we are doing here is calling the home view controller's method addObserver() to register the observer before navigating, and after that, we are posting the NotificationCenter.default.post. By doing this way the observer gets registered first.
In First View Controller add this function in your button click
func navigate(){
let storyBoard = UIStoryboard(name: "travelApp", bundle: nil)
let VC = storyBoard.instantiateViewController(identifier: "home") as! homeViewController
VC.addObserver()
NotificationCenter.default.post(name:Notification.Name("myNotificationName"), object: nil, userInfo: ["hello": "world"])
self.navigationController?.pushViewController(VC, animated: true)
}
In Second View Controller do this
class homeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
func addObserver(){
print("addObserverCalled")
NotificationCenter.default.addObserver(self, selector: #selector(onNotification(notification:)), name: Notification.Name("myNotificationName"), object: nil)
}
#objc func onNotification(notification:Notification){
print("NotificationCenter")
print(notification.userInfo!)
}
}
you are posting notification before your view controller is initialized.
there is no need to post notification for calling the method of vc2. instead, directly call the method.
In VC1
#IBAction func nextButtonClicked(_ sender: Any) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let vc2 = storyBoard.instantiateViewController(withIdentifier:"ViewController2Id") as ViewController2
vc2.print1Method()
navigationController?.pushViewController(vc2!, animated: true)
}
}
In VC2
func print1Method() {
print("Method called from VC 1")
}
also for the back button, you are doing it wrong. simply pop the view controller from the stack
#IBAction func backToVC1(_ sender: Any) {
self.navigationController?.popViewController(animated: true)
}

How to go from child UIViewController to parent UIViewController and from there to another child UIViewController

I have a parent UIViewController(MainVC). From there I have 2 segue to 2 UIViewControllers: FirstVC (identifier: goFirstVC) and SecondVC (identifier: goSecondVC)
In FirstVC I have a button Save and when I click it I want to dismiss the FirstVC and to go on SecondVC.
Here is my code:
#IBAction func saveBtnTapped(_ sender: UIButton) {
//navigationController?.popViewController(animated: true)
let destinationController = self.storyboard?.instantiateViewController(withIdentifier: "goSecondVC") as! SecondVC
let presentingVC = self.presentingViewController
self.dismiss(animated: false, completion: { () -> Void in
presentingVC!.present(destinationController, animated: true, completion: nil)
})
}
Here is the design for my issue:
You can use setViewControllers to keep the parent only and the SecondVC
let destinationController = self.storyboard?.instantiateViewController(withIdentifier: "goSecondVC") as! SecondVC
self.navigationController?.setViewControllers([self.navigationController!.viewControllers.first!,destinationController], animated: true)
There are many methods but one of the generic one is to use delegates and protocols. Use the following code in your classes.
Add Following code in the first VC
protocol SecondVCDelegate : AnyObject {
func goToSecondVC()
}
class FirstVC: UIViewController {
var Delegate : SecondVCDelegate!
#objc func save() {
Delegate.goToSecondVC()
}
}
//In second view
Add following code in MinVC
class MainVC: UIViewController {
override func viewDidAppear() {
self.performSegue(withIdentifier: <yourSegueIdentifierToFirstVC>, sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == <yourSegueIdentifierToFirstVC> {
let cont = segue.destination as! FirstVC
cont.Delegate = self
}
}
}
extension MainVC : SecondVCDelegate {
func goToSecondVC() {
self.performSegue(withIdentifier: <yourSegueIdentifierToSecondVC>, sender: nil)
}
}
//This is MainVC
I think you should present second from first rather than dismissing first then presenting second.
when you will present second from first then if you wanto go back to main you can simply dismiss 2 veiwcontrollers at once without any hack.
try it:
We can control our presented controllers with navigation controller calling pushViewController and popViewController methods.
FirstVC.navigationController?.popViewController(animated: animated) // pops the top view controller
self.navigationController?.pushViewController(SecondVC, animated: true) // Pushes a view controller onto navigation's stack of controllers
In your case:
let destinationController = self.storyboard?.instantiateViewController(withIdentifier: "goSecondVC") as? SecondVC
self.navigationController?.popViewController(animated: animated)
self.navigationController?.pushViewController(destinationController, animated: true)

Not navigate to specific view controller?

I am trying to navigate to specific view controller but it is not working.
what i am doing is below
var viewControllers:[UIViewController]!
var firstView : UIViewController!
let storyboard = UIStoryboard(name: "Main", bundle: nil)
firstView = storyboard.instantiateViewController(withIdentifier: "FirstViewController") as! FirstViewController
viewControllers = [firstView]
#IBAction func BackAction(_ sender: Any) {
for aViewController in viewControllers {
if aViewController is FirstViewController {
self.navigationController!.popToViewController(aViewController, animated: true)
}
}
}
but not working .
Please help me where i am wrong.
Thanks

viewController is presented before the segue has been passed

I have a set of buttons and I want to send their title to the next page using segue . Here is what I have done :
// in my first page i have :
#IBAction func btn_language_action(_ sender: UIButton) {
let chosen_language = sender.currentTitle!
print(chosen_language)
let game_level = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "game_level") as! game_level_viewController
self.present(game_level, animated: false, completion: nil)
performSegue(withIdentifier: "GL", sender: chosen_language)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let game_level_viewController = segue.destination as! game_level_viewController
game_level_viewController.language = sender as? String
}
and in the second view I have :
#IBOutlet weak var lang: UILabel!
var language : String?
override func viewDidLoad() {
super.viewDidLoad()
setUI()
}
func setUI (){
lang.text = language
print(language )
}
I can pass the information successfully however , however the view has already been loaded before I receive what was sent . Any idea how can I fix this ?
In the console I have :
language ==> nil
language ==> Optional("Persian")
the problem is due to this line
self.present(game_level, animated: false, completion: nil)
why you are presenting this controller even if you have given segue to the controller
Remove this two lines and all will work good
let game_level = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "game_level") as! game_level_viewController
self.present(game_level, animated: false, completion: nil)
You have written two lines which both are used for transition between view controllers, So when you use segue don't present viewcontroller simultaneously.
Problem in your code is in this line, try to remove below code.
self.present(game_level, animated: false, completion: nil)

How to dismiss the current ViewController and go to another View in Swift

I am new to Swift and I want to know how to dismiss the current view controller and go to another view.
My storyboard is like the following: MainMenuView -> GameViewController -> GameOverView. I want to dismiss the GameViewController to go to the GameOverView, not to the MainMenuView.
I use the following code in my MainMenuView:
#IBAction func StartButton(sender: UIButton) {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewControllerWithIdentifier("GameViewController") as! GameViewController
self.presentViewController(nextViewController, animated:true, completion:nil)
restGame()
}
In the GameViewController, I use this code, but it doesn't dismiss the GameViewController.
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewControllerWithIdentifier("GameOverView") as! GameOverView
self.presentViewController(nextViewController, animated:true, completion:nil)
This is My GameOverView Code :
class GameOverView: UIViewController{
// save the presenting ViewController
var presentingViewController :UIViewController! = self.presentViewController
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func ReplayButton(sender: UIButton) {
restGame()
didPressClose()
}
#IBAction func ReturnMainMenu(sender: UIButton) {
Data.GameStarted = 1
self.dismissViewControllerAnimated(false) {
// go back to MainMenuView as the eyes of the user
self.presentingViewController.dismissViewControllerAnimated(false, completion: nil);
}
/* let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewControllerWithIdentifier("MainScene") as! MainScene
self.presentViewController(nextViewController, animated:true, completion:nil)*/
}
func restGame(){
Data.score = 0
Data.GameHolder = 3
Data.GameStarted = 1
Data.PlayerLife = 3.0
Data.BonusHolder = 30
Data.BonusTimer = 0
}
func didPressClose()
{
self.self.dismissViewControllerAnimated(true, completion:nil)
}
override func shouldAutorotate() -> Bool {
return false
}
deinit{
print("GameOverView is being deInitialized.");
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
Any suggestions?
What you can do is let the GameOverView be presented, after all when you presenting it the GameViewController is below in the hierarchy, and then in your GameOverView run the following code to close both when you want to dismiss the GameOverView, like in the following way:
#IBAction func ReturnMainMenu(sender: UIButton) {
// save the presenting ViewController
var presentingViewController: UIViewController! = self.presentingViewController
self.dismissViewControllerAnimated(false) {
// go back to MainMenuView as the eyes of the user
presentingViewController.dismissViewControllerAnimated(false, completion: nil)
}
}
The above code need to be called when you want to dismiss the GameOverView.
I hope this help you.
The below code will take you to the main VC, Here's a tried and tested piece of code.
self.view.window!.rootViewController?.dismiss(animated: false, completion: nil)

Resources