define specific TabBarController that isn´t the initial VC in AppDelegate - swift - ios

In my App there is a TabBarController that ins´t the initial VC.
I wanted to save the new tab order when the user didEndCustomizingViewControllers.
I found a recently asked question: How to: Save order of tabs when customizing tabs in UITabBarController
I used the code of Rickard Elimää which defines the TabBarController as the initial Controller in AppDelegate :
let tabBar: UITabBarController = self.window?.rootViewController as! UITabBarController
For testing whether the code to save the order works I set the TabBarController as the initial Controller and it works.
But the UITabBarController should´t be the initial VC.
So my question is how to define that TabBarController, so that the code works.
Thanks for any help

I found the best way to do this is to implement UIViewControllerTransitionDelegate. This will handle animation as well as passing the rootViewController. It's a tad more complicated than just passing the rootViewController (as I originally had done), but it was worth the time to switch to this in the long run.
Apple Documentation.
www.raywenderlich.com (an implementation example)

Related

Proper Login Flow and TabBar VC

I am having a few issues accessing the tab bar from the App Delegate to setup the Home Screen Quick Actions. Here is the line of code that I am using to access the tab bar. It is returning false.
guard let tabBarController = self.window?.rootViewController as? UITabBarController else {return false}
My tab bar is not my initial VC when the app launches. I have a loading screen during which we authenticate the user token and then it goes to either the login screen or the tab bar controller (which is also the main part of the app) depending on whether the token gets authenticated.
What is best practice for setting up an app with a login screen? The way we are doing it now works fine but I can change it if there is a better way. We are also using Branch for deep linking.
This is a opinion based question and might exist multiple answer to it each of which might be suitable for some specific scenario.
Approach 1 :
This is my personal Favorite does not mean that this is the only proper way of doing it. I prefer replacing the application's rootView Controller either with LoginVC or TabBarVC based on wether the token is valid or not. There are multiple answers in SO explaining how can u replace the application's rootVC with proper animation. Pasting the same code would be redundant here.
Why I use this approach?
Keeps my applications navigation controller stack clean and I don't keep any additional VC's in memory than whats actually required.
Approach 2 :
This is what many people use for the simplest reason that its simple to use but I personally doesn't prefer it. Modally present either Login VC or Tab bar (both of them might be embedded with UINavigationController obviously and you modally present their NavController's which obviously loads its embedded view controller).
Pros:
Easy to code.
You can always be sure that app's rootVC is always fixed and it has presented either LoginVC or TabBarVC. So parsing and accessing the VC's becomes fairly simple.
Cons:
The Landing VC which modally presents unnecessarily remains in Applications Navigation stack through out the apps life cycle. I clearly don't favor this.
EDIT :
As OP has clearly mentioned that he is using approach 2 and wants to know how to access specific VC in tab bars selected index am updating the code show the same.
Code is not intended for Copy paste, code might contain syntactic error. Code provided below is only intended for providing the idea.
Assuming your LandVC does not have UINavigationController embedded to it.
if let landVC = UIApplication.shared.keyWindow?.rootViewController {
if let presentedVC = landVC.presentedViewController {
if presentedVC is LoginVC {
//this is login VC
}
else if presentedVC is UITabBarController {
let currentlySelectedVC = (presentedVC as! UITabBarController).viewControllers?[(presentedVC as! UITabBarController).selectedIndex]
//now check what type VC it is and use it accordingly
}
}
}

Calling Functions From AppDelegate in swift3

I'm using OneSignal to process notifications in my app. This is initiated in AppDelegate.swift where there is a function that handles received apps:
let notificationReceivedBlock: OSHandleNotificationReceivedBlock = { notification in
print("Received Notification: \(notification!.payload.body)")
}
I then have a TabViewController with 5 tabs and each tab's navigationItem has a UINavigationBarButton that sends you to a messages view. I want to change the badge number in that button whenever a notification comes in to show that the user is getting new messages. I have a function inside every viewController (each of the 5 tabs) that will update this.
The problem is that the AppDelegate needs to call this function updateBadgeNumber and I don't know how to do this. Plus, some of the tabs may not even have been initialised yet. Does anyone know how to call functions in ViewControllers from the AppDelegate?
Thank you.
EDIT:
Found a solution thanks to #paulvs below. He linked me to an answer where I found this: Find Top View Controller in Swift
The answer depends on whether your UITabBarController is the root view controller of your app.
If your UITabBarController is the root view controller, you likely have a reference to it in your app delegate and can use UITabBarController's selectedViewController property to get the current view controller and then set the badge value. (If you don't have a reference, you can probably cast window.rootViewController to UITabBarController to access it.)
If it's not, you can use code like this to find the currently visible view controller. (Then to access the UITabBarController, you can probably cast parentViewController to a UITabBarController, and from there access the other view controllers and set their badge values, too.)

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.

How to set the initial view controller after a successful login in Swift

My storyboard layout is like this:
navigationcontroller -> logincontroller -> tabbarcontroller -> first/second/third view controller
What I really want to do is to show the Tabbarcontroller after a successful login the next time when user launches the app.Plus, I've searched for a lot of solutions and most ones showed to use
rootView = storyboard.instantiateViewControllerWithIdentifier("MainView") as UITabBarController
window.rootViewController = mytabbarcontroller
and many reminded that this will break the segues.
I really don't know how to do that,please help me out!
In
didFinishlaunching method of AppDelegate use some bool to check if the login session is active.
Based on that choose you rootViewController

iOS switch ViewControllers of same data hierarchy

I struggle on the concept of an app. There is basically a ViewController for data input. The base ViewController has an embedded NavigationController to provide a ToolBar. The Toolbar is supposed to have three buttons to open other ViewControllers presenting the same input data differently.
My question now is what concept do I need to use to be able to start each ViewController (1-3) from the base ViewController and to make it possible to switch between the ViewControllers at the last Hierarchy Level.
I know how to call ViewControllers but how do I implement an additional Toolbar in ViewController 1-3 for switching between the VC 1-3 (another Navigation Controller?)? And how do I make sure that VC 1-3 are not dismissed and reopend again when switching between VC 1-3? How would all open VCs (1-3) closed (dismissed) once the base ViewController is called again by the user?
I try to find some helpful answers in the Apple doc but I failed so far.
Thanks!

Resources