Segue Between View Controllers based on Condition - ios

I am a new iOS developer and I'm writing an application that involves user login. Essentially, I would like to set the next view that the user sees based on a condition from the backend.(The conditional portion of this problem has already been solved, I successfully received the data through an Alamofire http get request)
My question is that, if I want to segue from one view controller to another, does it have to be sequential? I created two segues from the initial view controller to the two separate view controllers. However, if I try to segue to the view controller (that is not sequentially after) I receive the following error:
libc++abi.dylib: terminating with uncaught exception of type NSException
What would be the proper way to implement what I'm trying to do?

Sure you can't segue to a view controller that is not sequentially after , better way to solve your problem is to embed the initial VC inside a navigationController whenever you want to load any view controller , you can do this
let vc = self.storyboard?.instantiateViewController(withIdentifier: "anotherView")
self.navigationController?.pushViewController(vc!, animated: true)
where identifier is the storyboardID of the VC you want to show in above case it's anotherView
Here is where you set it

I do not understand much English, but I think it means that you want to change the view sequence and the rootController.
When the user is not logged in, the rootController will be the one marked as initial.
After login, we need to change the rootController to the new view sequence by starting the first controller.
Something like that.
if(uselogin)
{
var delegete = UIApplication.shared.delegate as! AppDelegate
delegete.window?.rootViewController = newViewController
presentViewController
}

Related

How to access previous view controller from a dismiss command

Throughout my app I use a navigation controller to push and pop my view controllers. When I pop from one ViewController, I check to see if I can reload the data in the previous one with this:
_ = self.navigationController?.popViewController(animated: true)
if let previousViewController = self.navigationController?.viewControllers.last as? AnimalsVC {
previousViewController.tableView.reloadData()
}
This works every time, but now I need to do the same with another view, but it's not apart of the navigation controller because I modally present (instead of pushing it to the navigation controller). Now there's no way I can access the previous ViewController like before and I can not figure out how to access the previous ViewController.
How can I access the previous ViewController to reload the tableview like the example code without accessing the navigation controller?
I know this is possible with notifications, but I prefer not to use that method if possible!
First of all, It's not necessary to access the previous ViewController to reload tableview(or any other func)
I recommend you to use Delegate to achieve the same feature.
Holding a reference to the previous viewController in the way you mentioned will make your app very hard to maintain when your app gets more complicated.
You can call tableview.reloadData() in viewWillAppear method in the controller that you present modally

How to segue to a different view controller on the last page in swift?

I have two different view controllers. One is programmatically created(slideshow) and the other is with the interface builder(Login page). On the slideshow, once the user reaches the last page, it should perform segue to the login page.
How can I make this work?
First you can't create a segue between a VC created in code and another created in IB ( as both should be in IB ) , you must present it like this
let login = self.storyboard?.instantiateViewController(withIdentifier: "loginID") as! loginViewController
and use
self.present(login, animated: true, completion: nil)
OR use this to completely clear the stack of shown VCs
UIApplication.shared.keyWindow?.rootViewController = login
If two view controller don't need to transmit data, you can just change window root view controller to login page. Of course, with animation would be better.

EXC_BAD_ACCESS in UISplitViewController after unwind segue, then returning

This is an application who interface consists of a tab controller (which has the rest of the interfaces residing in tabs), then two UISplitViewControllers with tables in them, and lastly a simple UIView with a button that allows the user to logout. The logout is implemented with an unwind segue back to the initial view controller (which is a login screen), and setting the root view controller of the app to a freshly instantiated version of the initial view controller (the login interface). After using this logout, then logging back in, the application immediately crashes with an EXC_BAD_ACCESS exception.
I have tried multiple different ways of implementing the behavior of the logout button (to segue back to the login screen) but every attempt has failed with the same results.
By following the stack trace, I was able to determine that the crash is occurring in the setCollapsedState method of the UISplitViewController. This is not a custom implementation of a split view controller, just the default one.
In case this is relevant, I am setting one of the child view controllers of the parent view controller as the delegate, however when I tried changing this and not setting the delegate, nothing improved.
I am not sure what other information I can provide, other than this code snippet of the logout button. If there's any other information that I can supply, please let me know and I will post edits.
#IBAction func signoutButtonPressed(_ sender: Any) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.signOut()
var rootVC = appDelegate.window?.rootViewController
rootVC?.removeFromParentViewController()
rootVC = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
appDelegate.window!.rootViewController = rootVC
}

ViewControllers are not destructing

I've got a serious problem with my iOS app.
I have a login logic in my application. When logging in and then logging out, some view controllers are not destructing. This causes some issues, for example, some events that I emit using NSNotifcationCenter are emitted few times. These issues are avoidable, but I really want a solution to avoid some view controllers to stay open in the background without me controlling it.
The way control the login logic is as follows:
In the app delegate start function, if the user is already logged in, I set the root view controller to the main usable view controller. Therefore, I'm not doing anything and the root view controller is set to the login view controller navigation controller through the storyboard.
When the user logs off, I use a modal segue to transition the view controller back to the login view controller navigation controller.
As you may understand I'm using storyboards, swift and the newest iOS.
My logout code is segue that take me to the LoginViewControler:
self.performSegueWithIdentifier("Logout", sender: self)
My app delegate code:
if (userDefaults.valueForKey("uid") != nil) {
let tabBarView = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("TabBarViewController") as! TabBarViewController
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = tabBarView
}
What am I doing wrong?
I would appreciate help :)
EDIT
I even tried just setting the root view controller in the logout action and that didn’t help either. How’s that even possible?
This is how I do the logout now:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let newRootViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LoginNavigationController") as! UINavigationController
appDelegate.window!.rootViewController = newRootViewController
Adam H. is right. If that doesn't work, then check for IBOutlets and delegates that have strong relationships, and change them to weak relationships. i.e.
#IBOutlet weak var collectionView: UICollectionView!
Without the weak keyword the view controller will never be disposed.
Depending on how your project is setup, if you are using a navigation controller (which I recommend) every time someone logs out you would put
dispatch_async(dispatch_get_main_queue()) {
self.navigationController.popToRootViewControllerAnimated(true)
}
That will pop everything off the navigation stack, which will dispose of all view controllers (unless you have strong relations, then they won't be disposed)
No matter how you choose to manage your trasitions , don't forget to add/ remove the observer whenever the view controller apear/disappear.
If the logged in screen presents the login screen and the login screen presents the logged in screen then you will have a cycle that keeps piling on new view controllers. To solve this, one must not present the other, but unwind to it. Another possibility is to hold instances of each as singletons and only present those.
I implemented something like that not long ago and to me it seems you're abusing the UINavigationController life cycle.
After reading your question twice, if I understand it correctly, it seems you're initializing your login view controller as a UINavigationController which stacks-up view controllers. Once user logs out, you're keeping the stack, adding more ViewControllers to the stack using the performSegue.
You can avoid it by using two different scenes -
1) Login View Controller which stands by it self.
2) Main flow of your app - can start with UITabController/ UINavigationController, both or whatever.
In AppDelegate you check -
If user is logged in - do your logics and set the app rootVC to the main flow vc.
Otherwise you set the loginVC (UIViewController) to be the root.
This also allows you to pop the login VC anywhere in the main flow, when needed, without interfering with the main flow.
In your case the loginVC is always UINavigationController's root so you must popToRootVC every time you wish to see it or performSegue to it which is worse because then you create another instance of a UINavigationController and resources never get deallocated.
Obviously in programming, in most cases, there are many solutions to one problem. I'm sure your problem can be solved using your flow. I just think it's bad experience to stack a loginVC over a navigation controller.
Part of the problem is that setting a new rootViewController on the UIWindow doesn't remove the view hierarchy from the old root view controller. That leaves all sorts of strong references hanging around, and if you use Xcode's view debugging, you can see that the the old view hierarchy is still sitting there, behind the new rootViewController's view hierarchy.
Something like this should fix the problem for you and allow your view controllers to deinit:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let newRootViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LoginNavigationController") as! UINavigationController
appDelegate.window??.rootViewController?.view.removeFromSuperview()
appDelegate.window??.rootViewController?.dismissViewControllerAnimated(false, completion: nil)
appDelegate.window??.rootViewController = newRootViewController
get rid of ARC file by file in build settings or wholesale
per project (it seems that you can have non ARC project but have weak references
while at it: not sporting I suppose but you can have both).
Then override retain and release in the problematic view controller
and see who hold the extra reference by breaking in the overriden
retain and release. It should be an eduficational experience.
The lazy approach is to kill ARC just for the VC in question.
I'd be curious to see how this works for VCs written in swift ;-)
Me thinks it's yet another reason to stay in objc domicile a while longer
until/if swift compiler and runtime solidifies (if ever).
Hope this helps anyone.
PS: It takes forever to compile some swift file in my project and I have NO
idea which swift file is causing this. Duh.
As yet indicated there aren't a lot informations for provide correctly the solution to your question.
I can suggest you to change your approach. I made a similar workflow using a UINavigationController (navigationController) launched from AppDelegate, inside of if we are logged in I put as ViewControllers :
(where self is navigationController and rootViewController is another UINavigationController)
self.setViewControllers[loginViewController, rootViewController]
If your are not logged in you put only loginViewController:
self.setViewControllers[loginViewController]
in this case you can put the rootViewController where the user is logged in.
This is my 2cent.
I like having a root VC which is just blank. When the app starts, root VC immediately displays login VC as a child VC of root VC. When the user successfully authenticates, the login VC notifies root VC, which then adds main VC as a child of root VC, transitions (with a nice animation) from login VC to main VC (using [self transitionFromViewController: toViewController: duration: options: animations: completion:]), and then removes login VC as a child and discards it. On logout, main VC notifies root VC which then does the same thing in reverse. So most of the time you only have either login VC or main VC instantiated; the only time they are both instantiated is during the transition.
I find segues are useful for building quick prototypes, but for production apps I prefer not to use them.
I Guess the ViewControllers Stack of you will go like this:
- 1st launch: LoginVC
- After login: LoginVC - TabarVC
- Click Logout: LoginVC - TabarVC - LoginVC
....
So your below code should work:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let newRootViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LoginNavigationController") as! UINavigationController
appDelegate.window!.rootViewController = newRootViewController
but it's not:(. In my opinion, You should always let the tabarVC rootViewController. And check in TabarVC, if user is not loged-in or user pressed logout, Present loginVC and dismiss it instead of performSegue.
You must remove your :
self.performSegueWithIdentifier("Logout", sender: self)
Instead of it, you can overriding this segue method, this is enough:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
userDefaults.removeObjectForKey("uid")
if segue.identifier == "Logout" {
let newRootViewController = segue.destinationViewController
// newRootViewController is optional in case you want to pass vars
// do whatever you want with your newRootViewController
}
}
About your NSNotification there are two methods to remove it:
NSNotificationCenter.defaultCenter().removeObserver(self, name: "NotificationIdentifier", object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self) // Remove from all notifications being observed
In Swift, you can put the removeObservers in the new and special deinit method.

Navigate with NavigationViewController

I would like to know how to properly navigate in an application using a NavigationController, to not instantiate a ViewController each time I call it for example:
In all know applications such as Instagram, when you click on a Toolbar Item, you access a viewController, but the state of this one is saved, if you were on a photo, it still is on this photo after you went to settings, post photo etc. And in my application, my NavigationController instantiate a new ViewController each time I want to access it, and it is time Consuming when you load data in ViewDidLoad for example.
Is there someone who could help me please ?
If you do not want to create a new controller - do not create it!
Create your controllers once and then reuse them. For example you can declare your controllers as a singletons:
let vc1 = MyViewController1()
let vc2 = MyViewController1()
let vc3 = MyViewController1()
And when you need to push some controller do not create a new one but reuse already created:
navigationVC.pushViewController(vc2, animated: true)
UINavigationController will throw an error if you will try to push a controller which is already in a navigation stack. So you will have to remove it from the navigation stack first:
// vc2 - is a controller that could be in the navigation stack and should be pushed once more
var controllers = navigationVC.viewControllers
if let index = controllers.indexOf(vc2){
controllers.removeAtIndex(index)
}
navigationVC.setViewControllers(controllers, animated: false)
then you can push vc2 as usual. If it is possible in your app you should also check a case if vc2 should be pushed right after vc2.

Resources