IOS - How to jump between viewControllers in different scenarios - ios

I have 5 viewControllers(A,B,C,D,E), all these viewControllers are programmatically connected, and i could push and pop between them successfully.
Using:
navigationController?.pushViewController(loginVc, animated: true)
_ = navigationController?.popViewController(animated: true)
NOTE: ViewController A appears first as a default initial viewController.
Now, what i want is, when the user installs the App, only for the first time the viewController A must be shown as the initial viewController, rest of the times when the App is opened, the viewController D must be the initial viewController and from there i should be able to jump between previous viewControllers. How can i implement this. Im using Xcode 8.2, Swift 3.0
Thanks in advance.

In order to do that, you simply could add a boolean to your NSUserDefaults, using the following code:
let defaults = UserDefaults.standard
if (!defaults.bool(forKey: "firstTime")) { //will be false if does not exist yet
navigationController?.pushViewController(yourDesiredVC, animated: true) //push desired vc
defaults.set(true, forKey: "firstTime") //set the key so it never executes again
} else {
navigationController?.pushViewController(yourDesiredVC, animated: true) //push the other vc
}

Your question doesn't really say very much without some code, but one suggestion (given the current quality of the question) would be to use UserDefaults. Add your current version of the app to a key called e.g. LatestVersion. Compare it at launch with the apps current version, if they don't match, show ViewControllerA, if not show ViewControllerB.
Another way is just saving launchedForFirstTime. If its not set show ViewControllerA, however the above would take in account future versions of the app where you might want to show that view as well.

You can keep a value in UserDefaults to keep track of the returning users and check if it's there:
if let returning :Bool = UserDefaults.standard.bool(forKey: "initial_controller_shown") {
//returning user flow
} else {
//new user flow
}
A common place to check for this is in the applicationDidBecomeActive or didFinishLaunchingWithOptions

when first time your app launched then use a flag and store some value in it so that next time when your app run then you can check that whether user visit the app for the first time or not .. Now after that go to appDelegate and paste the following code in DidFinishLaunchingWithOption...
if yourFlag == true
{
let mainStoryboard: UIStoryboard = UIStoryboard(name: "MainStoreyBoard", bundle: nil)
let controller = mainStoryboard.instantiateViewController(withIdentifier: "StoreyBoardIdofYourViewController") as! UINavigationController
self.window?.rootViewController = controller
}
This will launch D viewcontroller .....

You can check it in your app delegate.m file whether the app installed first time ViewController A will appear as a initial view controller else view controller D. Check it in the following function:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
//check the initial view controller here//
return true
}
Swift 3:
let launchBefore = UserDefaults.standard.bool(forKey: "launchBefore")
if launchBefore {
print("Not first launch.") } else {
print("First launch, setting UserDefault.")
UserDefaults.standard.set(true, forKey: "launchBefore") }

You are using one Navigation Controller so it will be hard to implement your behavior. It will be much easier for you to use separate View Controllers for View A and D and call them using:
present(vc, animated: true)
and dismiss calling:
dismiss(animated: true)

Related

How to call ViewControllers in regular sequence at AppDelegate.swift

now I am developing messenger
MessengerBox is tableViewController, when user tap the one of the cell, then chatRoomViewcontroller is presented.
if the app is not running and message arrived, then push notifications show. And user Tap the notification, App shows chatRoomViewcontroller directly.
Initially, I implemented this code by using window.rootViewController
But the problem happened. when I tap Back Button of chatRoomviewController, change is not happened because this view controller is rootview and its presentingViewController is empty!
so I fixed it like below
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
///some code for notification..
let mVC //this is MessengerBoxViewcontroller
let crVC //this is ChatRoomViewController
CRVC?.sender = "asdf"
do{
self.window?.rootViewController = mVC
self.window?.makeKeyAndVisible()
defer{
mVC?.presentChatRoomVC()
}
}
/// some code...
}
It works! But I'd like to know better way.
And Also I think I should study How window and viewcontrollers works.
please recommend me the better way, and reference documents.
Thank you.
You need to handle back button action programatically in ChatRoomViewControllerto solve your problem :
On back button Click :
Check for MessengerBoxViewcontroller is present in navigationController stack.
if MessengerBoxViewcontroller is in navigationController stack then pop to move back to MessengerBoxViewcontroller.
Else present MessengerBoxViewcontroller.
if let viewControllers = self.navigationController?.viewControllers {
for viewController in viewControllers {
if viewController.isKindOfClass(MessengerBoxViewcontroller) {
print("Your controller exist")
navigationController?.popViewController(animated: true)
}
}
}
else {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "messengerBoxViewcontroller") as! MessengerBoxViewcontroller
self.present(vc, animated: true, completion: nil)
}

Starting at a certain view if user is logged in iOS Swift

I am currently making a small login app using Firebase. I am currently having problems with my login page.
When my users already are logged in, when they open the app, I want to change the initial view controller, so that the user can go straight to the homepage.
So my question is, what line of code do I have to perform in order to do this?
override func viewDidLoad() {
super.viewDidLoad()
if FIRAuth.auth() ? .currentUser ? .uid == nil {
notLoggedIn()
}
}
func notLoggedIn() {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "Startpage") as!ViewController
self.present(nextViewController, animated: true, completion: nil)
}
There's a couple of ways you can do this. If you really want to change the initial view controller, you would want to NOT set an initial view controller in your storyboard, then in your app delegate's application(_:didFinishLaunchingWithOptions:) implementation, you would create a new Window object and set whichever view controller on it you want to present as the rootViewController. Then, you would call makeKeyAndVisible on that window object. Note that if you do it this way, you'll have to separately handle the case when they log out if you want to display your login window again. In that case you would just do the same thing again: make a new window object with your new ViewController object as the rootViewController and present it.
Another option is to check if they are logged in in your initial view controller's viewDidLoad method and then present your login screen if they aren't. This is what I do in one of applications where the app needs some data, either by logging into an account or manually adding it, before it can do anything.
EDIT:
Here's what my viewDidLoad, etc. looks like (note that mine project is in Objective-C, so I'm just kinda guessing without actually testing it what the correct Swift syntax is. You might need to make some adjustments) You have to dispatch the present call to the main queue because in viewDidLoad you (probably) don't have everything in order yet to actually present a new view controller (I did this quite a long time ago, so I don't recall exactly why it has to be dispatched, but because of the fact that we're already in the process of presenting the current view controller, it makes sense that you wouldn't be able to present another one at the same time. Maybe someone else can weigh in on this, because I really don't remember anymore.):
override func viewDidLoad() {
super.viewDidLoad()
if (!userLoggedIn) {
showLoginScreen()
}
}
func showLoginScreen() {
let loginViewController = storyboard?.instantiateViewController(withIdentifier: "Startpage") as! ViewController
DispatchQueue.main.async {
present(loginViewController, animated: true, completion: nil)
}
}
You can use this line of codes.
Keep in mind that you should add storyboard reference with identifier
named respectively for your need - goToLogin - in my case.
Hope It'll be helpful for anyone.
override func viewDidLoad() {
super.viewDidLoad()
Auth.auth().addStateDidChangeListener { auth, user in
if let user = user {
// User is signed in.
print("user signed in")
//Add the rest of the code here because after passig the caluses
// viewdidload will call another funxtions to it can crash
} else {
// User not signed in
self.performSegue(withIdentifier: "goToLogin", sender: Any?.self)
}
}
}

How to execute certain code when returning to a view swift

When reversing the segue of a view, I would like to have certain code performed. This is unlike viewdidload, or viewdidappear, because I don't want it to run when the app first launches, as it is my initial view controller, but only when returning from another view.
How do I go about achieving this?
Thanks.
You should use a delegate for this sort of thing, here is the first tutorial i saw on google about it but can google for some different ones. delegation is a key coding pattern that all iOS devs should know about and when to use.
So in your case, your first view controller will be a delegate of the second one, which will allow the second vc to execute methods on the first vc when it sees fit, and for this case i would assume before the second vc goes back to the first
the simplest way is to keep some boolean propery (e.g. Bool firstAppear) then initialize it with true in viewDidLoad method, and then in viewDidAppear:
if firstAppear {
firstAppear = false
return
}
You can use viewWillAppear and viewDidAppear methods. They called not just one time, but every time, your view controller appearing.
viewDidLoad called just one time after view controller loading
Let us consider two viewcontrollers VC1 and VC2. You are now in VC2 and trying to navigate back. in ViewWillDisappear of VC2 try this code.
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setBool(true, forKey: "mykey")
And now you are navigating back to VC1. In VC1 ViewWillAppear use this code
let defaults = NSUserDefaults.standardUserDefaults()
let mykey = defaults.boolForKey("mykey")
if mykey {
defaults.setBool(false, forKey: "mykey")
//execute your code here
}
First you can set this for example in your AppDelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
...
let alreadyLaunched = NSUserDefaults.standardUserDefaults().boolForKey("alreadyLaunched")
if alreadyLaunched {
print("This is not the first app launch.")
}
else {
print("First app launch, setting NSUserDefault.")
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "alreadyLaunched")
}
}
Then check wherever you want:
let alreadyLaunched = NSUserDefaults.standardUserDefaults().boolForKey("alreadyLaunched")
if alreadyLaunched {
print("This is not the first app launch.")
}

How to override initial ViewController on First Run thru AppDelegate

I am using the following code to detect whether the app is running for the first time:
// Detect First Launch
let firstLaunch = NSUserDefaults.standardUserDefaults().boolForKey("FirstLaunch")
if firstLaunch {
print("Not first launch.")
Show()
}
else {
print("First launch, setting NSUserDefault.")
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "FirstLaunch")
}
I am using the following function to show the ViewController:
func Show(){
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier(“myViewController”) as! MyViewController
self.window?.rootViewController?.presentViewController(vc, animated: true, completion: nil)
}
I put both code under: func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
However, when I build and run the app, the initial view controller is shown as if nothing happened. I want “myViewController” to be shown FIRST (during the app’s FIRST EVER LAUNCH on the device.) After that first launch, however, the “myViewController” won’t be shown anymore, unless the user uninstalls and freshly reinstalls the app.
Does anyone know how to do it?
Since you want to show the custom screen on first launch and then dismiss it, I would run this code in your normal root VC's viewWillAppear(animated:Bool) method
EDIT:
Yes, you will have to modify the code a bit. For example, like so:
func Show(){
guard let sb = storyboard else { return }
let vc = sb.instantiateViewControllerWithIdentifier(“myViewController”) as! MyViewController
navigationController?.presentViewController(vc, animated: true, completion: nil)
}
After setting the value to user defaults, you have to save it using the synchronize method. This method is invoked periodically buy the system, but if you didn't synchronize before it was called, your value isn't saved.
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "FirstLaunch")
NSUserDefaults.standardUserDefaults().synchronize()
Documentation of synchronizefrom to Apple docs :
Writes any modifications to the persistent domains to disk and updates all unmodified persistent domains to what is on disk.
Discussion
Because this method is automatically invoked at periodic intervals, use this method only if you cannot wait for the automatic synchronization (for example, if your application is about to exit) or if you want to update the user defaults to what is on disk even though you have not made any changes.

Call ViewControllers Method or Segue from App Delegate (Swift)

I have created an app that uses storyboard and have successfully created a tableview and detail page which all works.
I would like it so that users swiping the localNotifications can be sent to the correct detail page within the app.
It appears that I can call functions from the ViewController but whenever they refer to themselves to update any details or perform a segue the app crashes.
The code i have in my ViewController is as follows:
func handleMessageNotification (messageID:String)
{
// this function should act as a conduit for the call from the delegate
println("notif messageID: \(messageID)");
self.performSegueWithIdentifier("showMessageDetail", sender: self);
self.messageView.reloadData();
}
and this is called from my appDelegate
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
if (application.applicationState == UIApplicationState.Background || application.applicationState == UIApplicationState.Inactive)
{
var rootViewController = self.window!.rootViewController
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var setViewController = mainStoryboard.instantiateViewControllerWithIdentifier("ViewController") as ViewController
rootViewController?.navigationController?.popToViewController(setViewController, animated: false)
setViewController.handleMessageNotification(messageID);
}
}
the println works correctly but performSegue fails (fatal error: unexpectedly found nil while unwrapping an Optional value) and the messageView.reload() fails (same error).
How do I get a notification to fire the app to the correct place upon opening?
This solution 'Get Instance Of ViewController From AppDelegate In Swift' uses much of the same but mine will not allow access to anything with the ViewController.
======= Update - Solution =======
For anyone else with this issue. Following on from what Gabuh had suggested below; the full solution for me was to do the following:
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
if (application.applicationState == UIApplicationState.Background || application.applicationState == UIApplicationState.Inactive)
{
let navigationController = application.windows[0].rootViewController as UINavigationController;
navigationController.popToRootViewControllerAnimated(false); // I need to push back to root before performing segue
let rootViewController = navigationController.visibleViewController;
rootViewController.performSegueWithIdentifier("showMessageDetail", sender: self);
}
}
this also allows me to make calls on functions in the view such as in the original example e.g.
rootViewController.handleMessageNotificaion(messageID);
or
rootViewController.messageView.reloadData();
You are creating an instance of a new ViewController. And you are trying to call a function on that ViewController, that is trying to perform a segue, when the ViewController is not even displayed. I think that's not going to work...
If you want to perform a segue from your detail view, you have to access to the current instantiated view Controller and perform the segue from it.
If you want to know how to get the instance of the UIViewController that is being displayed, in this post there are several ways that show you how to do it:
Get the current view controller from the app delegate
In pseudo-code, in your appDelegate:
1- get current UIViewController instance (not new one)
2- perfom segue from that view controller (or present modally or whatever transition you want)
In Swift 3:
guard let rvc = self.window?.rootViewController as? VCName else { return }
rvc.methodInYourVC()
The above assumes
That you don't want to create a whole new instance of VCName, just want a reference to it
That you know the VC you want to reference is your root VC

Resources