Remove a ViewController from navigationStack and add a new instance of it - swift

I have a SongNamesViewController with navigationController embedded. When a song is chosen from a list I open PlaySongViewController and add to the navigationController using following function:
func openPlaySongViewController() {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let playSongViewController = storyBoard.instantiateViewController(withIdentifier: "playSongViewController")
self.navigationController?.pushViewController(playSongViewController, animated: true)
}
Now, a message arrives via remote push-notification. The user taps on the push-notification icon and then I display the song using "openPlaySongViewController()" function. If another push-notification comes and then I display another PlaySongViewController on top of existing PlaySongViewController.
Current flow: (NavigationController)->SongNamesViewController>PlaySongViewController->PlaySongViewController
How do I remove the existing PlaySongViewController that is on the navigationController before adding a new instance of PlaySongViewController?
I tried the following but the PlaySongViewController that is on navigationController does not go away.
for viewController in self.navigationController!.viewControllers {
if viewController.isKind(of: PlaySongViewController.self) {
viewController.removeFromParent()
}
}

topViewController returns what is on top of the navigation stack. So you should pop it if needed.
func removeLastControllerIfNeeded() {
guard navigationController?.topViewController is PlaySongViewController else { return }
navigationController?.popViewController(animated: true)
}

Related

Making a new view controller appear after interstitial ad is dismissed

I am building an iOS app in swift with Xcode that has a button that, when pressed, will (among other things) show an interstitial Admob ad, and then go to a different view controller. Right now, when I press the button, an interstitial ad will appear. However, when I close the ad, the view is still on the original page. If I click the button again, then I am taken to the second view controller. Sometimes, however, I have to dismiss three or four ads before I get to the second view controller. How can I make it so that, after I dismiss the interstitial ad, I go straight to the second view controller?
Here is the current code:
#IBAction func postPressed(_ sender: Any) {
if contact.text?.isEmpty == true {
print("No contact")
return
}
if avail.text?.isEmpty == true {
print("No availability")
return
}
uploadSpot()
}
func uploadSpot() {
let key = Int.random(in: 20001..<30000)
let object: [String: Any] = [
"Contact": contact.text!,
"Availability": avail.text!,
]
geoFireRef.child("\(key)").setValue(object)
let location = manager.location!
geoFire.setLocation(location, forKey: "\(key)")
if interstitialAd?.isReady == true {
interstitialAd?.present(fromRootViewController: self)
}
let storyboard = UIStoryboard(name: "SpotLoaded", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "SpotLoaded")
vc.modalPresentationStyle = .overFullScreen
present(vc, animated: true)
}
The import part is in the function "uploadSpot()," where
if interstitialAd?.isReady == true {
interstitialAd?.present(fromRootViewController: self)
}
presents the ad and
let storyboard = UIStoryboard(name: "SpotLoaded", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "SpotLoaded")
vc.modalPresentationStyle = .overFullScreen
present(vc, animated: true)
switches the viewcontroller. I have tried reversing the order if these two (i.e. putting let storyboard, etc., before if insterstitialAd), but that just takes me to the new view controller and the ad never appears. I have also tried putting the let storyboard chunk inside of the brackets following the if interstitialAd. This does not help either. If I put it after interstitialAd?.present(fromRootViewController: self), I have the same problem where the ad shows but the view controller does not change. If I put it after, the viewcontroller changes but the ad never shows.
As the main purpose of interstitial ads is to appear in between views of an app, I do not think what I am trying to do should be so difficult. I think there must just be something I am missing or do not understand. Please help in whatever way you can.

Instantiating deep View Controller with Tab bar and Navigation

I am using Xcode 11 and Swift 5.
Upon receiving an APNS notification, I need to jump to a view controller deep in my storyboard from AppDelegate. This viewController (chatVC) is behind a tabbar and a navigation controller and several other view controllers. See the image below. I know how to check the notification for tags and how to use launchOptions in AppDelegate to trigger the jump. But I am struggling with how to establish the context for that last view controller so that the user can use the back button all the way back to the tab bar controller.
I have read many SO answers, and tried many approaches but none seem to have the same embedding of a nav controller inside a tab bar controller. Here is my code in AppDelegate (after reading the tag in the notification):
if tag == "CS" {
// Set up a Chat View Controller.
if let chatVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.chatVC) as? NewChatViewController,
let tabBarVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.tabBarController) as? UITabBarController,
let csVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.csViewController) as? CustomerServiceViewController,
let helpVC = UIStoryboard(name:"Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.helpVC) as? HelpViewController
{
// Set the customer service document Id.
chatVC.cs = cs
// Make the tabBarVC the Root View Controller
self.window?.rootViewController = tabBarVC
self.window?.makeKeyAndVisible()
// Select the Favorites Index.
tabBarVC.selectedIndex = 0
// Push the Customer Service VC on top.
tabBarVC.show(csVC, sender: Any?.self)
// Push the Help VC on top of Customer Service VC.
csVC.show(helpVC, sender: Any?.self)
// Push the the chat detail page on top.
helpVC.show(chatVC, sender: Any?.self)
}
}
}
return true
What can I do to jump to the chatVC and set up the navigation context beneath it so the back button can be used?
Here is how you can do it:
guard let tabBarVC = UIApplication.shared.windows.filter( {$0.rootViewController is UITabBarController } ).first?.rootViewController as? UITabBarController else { return }
tabBarVC.selectedIndex = 0 //you can select another tab if needed
guard let chatVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.chatVC) as? NewChatViewController else { return }
if let navController = tabBarVC.viewControllers?[0] as? UINavigationController {
navController.pushViewController(chatVC, animated: true)
}
You can also pass objects from your notification to your chatVC here before pushing it, in case you need to do that.
Hope this works for you!

Swift - How switch between storyboards with back button?

I'm in a storyboard and I need to go to another storyboard by tapping a button. Well, this I've already done, but I need to go back to previous storyboard. Here is my code:
func goContact() {
let storyboard = UIStoryboard(name: "FAQ", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "ContactViewController")
self.navigationController?.show(vc, sender: nil)
}
By tapping a button on my UITableViewCell (where I have a protocol), this function (goContact) is called and I switch to another storyboard (FAQ). But, how can I go back to main.storyboard?
I've already tried do this too:
func goContact() {
let storyboard = UIStoryboard(name: "FAQ", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "ContactViewController")
self.navigationController?.show(vc, sender: nil)
// self.navigationController?.pushViewController(vc, animated: true)
// self.present(vc, animated: true, completion: nil)
}
Use only storyboard property , it references to Main storyboard of the app or you can write it like let storyboard = UIStoryboard(name: "Main", bundle: nil)
To go back after push do
self.navigationController?.popViewController(animated: true)
You should push your new view controller onto the navigation controller using push, like in your commented-out code. That will cause the new view controller to have a back button, which will pop it for you without needing any extra code.

Push to ViewController from AppDelegate with stack and navigation Bar

I'm developing a messaging app and I'm having trouble implementing push notification behaviour.
What I want is that when a user taps on the notification, the conversation of that group opens. But there has to be a Navigation bar to go back to the list of contacts.
I already know how to extract information from the notification to get the contact name. I'm trying this in the function didReceive on AppDelegate.
So far what I've tried is:
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ChatViewController") as! ChatViewController
vc.contactName = name as String
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = vc
self.window?.makeKeyandVisible()
This open the right conversation and the messages appear, but UI elements are no responsive (can't write when tapping textfield, cant navigate to gallery when tapping button, etc.) and also there is no Navigation bar.
So after doing some research, I've tried this:
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ChatViewController") as! ChatViewController
vc.contactName = name as String
let rootViewController = self.window?rootViewController as! UINavigationController
rootViewController. pushViewController(vc, animated: true)
This doesn't work. There is no error message but the screen is just white.
This is my Storyboard:
I'm using SWRevealViewController for the side menu and the messaging part has a tabBarController, with the contact list on the 2ยบ tab.
EDIT: With the second method, there is an error.
Could not cast value of type 'SWRevealViewController to UINavigationController'
I think you should go with NotificationCenter to do this.
When the user click on the notification you should post a NotificationCenter in the didReceive like this:
NotificationCenter.default.post(name: .onMessageRecieved, object: notificationObject)
And based on which view controller you're in you should add an observer there in the viewDidLoad() func so it understands a message has been recieved like this:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(MyViewController.onMessageRecieved(notification:)), name: .onMessageRecieved, object: nil)
}
And you should have a func (onMessageRecieved) in the view controller to do the navigation like this:
func onMessageRecieved(notification: NSNotification){
// open the conversation
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ChatViewController") as! ChatViewController
vc.contactName = name as String
self.navigationController?.pushViewController(vc, animated: true)
}
This way you are doing the navigation from the view controller so the navigation will be there as well.
Could not cast the value of type 'SWRevealViewController to UINavigationController'
This issue is because of the "SWRevealViewController" is not a subclass of UINavigationController. So you can't cast it this way.
To solve the chat ViewController issue, you should modify your code like this.
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ChatViewController") as! ChatViewController
let nav = vc.navigationController
vc.contactName = name as String
let rootViewController = self.window?rootViewController as! UIViewController
rootViewController.presentViewController(nav, animated: true, completion: nil)
But the chat VC will be in the presenter mode. You should handle the dismiss.
You just try this and let me know is it working or not.

Show view controller from main storyboard

I have a case in one of my Swift files which runs the following method:
case .editProfile:
vc = PoiDetailViewController()
self.navigationController?.popViewController(vc,animated: true)
It doesn't seem to work when I run the code.
The view controller is located on the main storyboard and I would like PoiDetailViewController to display when this case is called.
If I understand it correctly, you should be able to achieve what you're looking for by present in your code. Like so:
case .editProfile:
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyBoard.instantiateViewController(withIdentifier: "PoiDetailViewController") // or whatever identifier you have given it
self.present(vc, animated: true, completion: nil)
That should work, however, make sure you set a storyboard ID for your view controller in the identity inspector.
No segues are required.
To set up a storyboard ID click on the View Controller you would like to use (in this case, PoiDetailViewController) and click the identity inspector icon and set a storyboard ID where it asks. I have attached an image so you can see where it needs to go (in the field marked 'Storyboard ID')
Hope that helps.
As per my Understanding you need to pop to specific View Controller if it is in stack and if not then you need to push to that controller
var loginVCFound:Bool = false;
let navigationController : UINavigationController! = self.window!.rootViewController as! UINavigationController;
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil);
let viewControllers: [UIViewController] = navigationController.viewControllers
for aViewController in viewControllers {
if aViewController is LoginVC {// Pass the name of your controller here
loginVCFound = true;
navigationController.popToViewController(aViewController, animated: true)
break;
}
}
if !loginVCFound { // change the identifier and VC with yours
let objLoginVC = mainStoryboard.instantiateViewController(withIdentifier: "LoginVC") as! LoginVC
navigationController.pushViewController(objLoginVC, animated: true)
}