Changing rootViewController causing weird behavior - ios

I am setting rootViewController like this in my app.
func setupMainView() {
let rootViewContorller = window?.rootViewController
if (rootViewContorller?.presentedViewController != nil || rootViewContorller?.presentingViewController != nil) {
rootViewContorller?.dismiss(animated: false, completion: nil)
}
let tabbarController = UITabBarController()
tabbarController.delegate = self
let homeViewController = HomeViewController()
let rewardsViewController = RewardsViewController()
let homeNVc = UINavigationController()
let rewardsNVc = UINavigationController()
homeNVc.viewControllers = [homeViewController]
rewardsNVc.viewControllers = [rewardsViewController]
tabbarController.viewControllers = []
tabbarController.viewControllers = [homeNVc, rewardsNVc]
tabbarController.selectedIndex = 0
self.window?.rootViewController = tabbarController
}
It is working fine. But I have to change rootViewController in the app like after registration etc. After that When I go to Debug View Hierarchy . I still see the registrationViewController there. And lets say If I change rootViewController 3-4 times all previous controllers are still there. So my question is How can I remove all viewControllers from memory before changing the rootViewController.

You can call this function this will clear all viewcontrollers.
self.window?.rootViewController?.dismissViewControllerAnimated(false, completion: nil)

Related

AppDelegate presents views twice

I'm implementing quick-actions for my app and for that I need to present viewcontrollers from the app delegate. I use code like this:
let viewController = storyboard.instantiateViewController(withIdentifier :"MyControllerID") as! MyController
let navController = UINavigationController.init(rootViewController: viewController)
if let window = self.window, let rootViewController = window.rootViewController {
var currentController = rootViewController
while let presentedController = currentController.presentedViewController {
currentController = presentedController
}
currentController.present(navController, animated: true, completion: nil)
}
If the app is in background and the quickaction is clicked everything works fine, but if the app hasn't been started yet and the quick-action gets clicked the view is presented twice.
What am I doing wrong here?
// Edit 1: This code is in a function and the function is called in applicationDidBecomeActive
In didFinishLaunchingWithOptions I check if the app was launched via Quick-Actions like this:
if let shortcutItem = launchOptions?[UIApplicationLaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem {
launchedShortcutItem = shortcutItem
}
The launchedShortcutItem is a variable:
var launchedShortcutItem: UIApplicationShortcutItem?
// Edit 2: For those who don't know what I mean by "Quick-Actions" here are the Apple Pages for them: Guideline, Documentation

Swift 4 - How to set and show a new root view controller from current view controller

Struggling a couple of days with this...
I have set a rootVC from AppDelegate in didFinishLaunchingWithOptions and this is working fine.
Now from that rootVC I want that, if some condition is met, eg. if x=y a new rootVC is set and displayed.
I stack overflowed long long time, found different solutions, but none is working.
The below is compiling, and executing, I checked with breakpoint, but nothing shows up in the app.
animated Bool false
self Regional2.IrelandViewController 0x0000000102b0ff30
newViewController Regional2.IrelandMain 0x0000000102d0d300
customViewControllersArray NSArray 0x00000001c000b620
ObjectiveC.NSObject NSObject
Exception State Registers
far unsigned long 0x00000001b0a7ba78
esr unsigned int 0x92000007
exception unsigned int 0x00000000
Floating Point Registers
General Purpose Registers
and the piece of the code . . .
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let newViewController = self.storyboard?.instantiateViewController(withIdentifier: "IrelandMain") as! IrelandMain
let customViewControllersArray : NSArray = [newViewController]
self.navigationController?.viewControllers = customViewControllersArray as! [UIViewController]
self.navigationController?.pushViewController(newViewController, animated: true)
}
Please notice that navigationController show produces the same output. Nothing changes.
That code works for me.
It simply replaces the whole UIWindow.
When I tried to only replace the rootViewController it did it but it created another one (so I had two loaded viewControllers in the view hierarchy)
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
// If we only reloads the rootViewController than the main window has 2 subviews
// The older cannot be removed and the new can be removed.
// The workaround I found is to replace the whole UIWindow with new one
let root = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateInitialViewController()
let newWindow = UIWindow()
appDelegate.replaceWindow(newWindow)
newWindow.rootViewController = root
}
And in your AppDelegate
func replaceWindow(_ newWindow: UIWindow) {
if let oldWindow = window {
newWindow.frame = oldWindow.frame
newWindow.windowLevel = oldWindow.windowLevel
newWindow.screen = oldWindow.screen
newWindow.isHidden = false
window = newWindow
oldWindow.removeFromSuperview()
}
}

"Attempt to present while already presenting" still appearing after checks?

let appDelegate = UIKit.UIApplication.shared.delegate!
if let tabBarController = appDelegate.window??.rootViewController as? UITabBarController {
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let signInVC = storyboard.instantiateViewController(withIdentifier: "SignInVC") as! SignInVC
guard !signInVC.isBeingPresented else {
log.warning("Attempt to present sign in sheet when it is already showing")
return
}
signInVC.modalPresentationStyle = UIModalPresentationStyle.formSheet
tabBarController.present(signInVC, animated: true, completion: nil)
}
This code can be called multiple times despite signInVC being presented. I already added this check:
guard !signInVC.isBeingPresented else {
log.warning("Attempt to present sign in sheet when it is already showing")
return
}
but it doesn't seem to prevent this error:
Warning: Attempt to present <App.SignInVC: 0x101f2f280> on <UITabBarController: 0x101e05880> which is already presenting <App.SignInVC: 0x101f4e4c0>
Your guard isn't a valid check. The isBeingPresented is being called on a brand new view controller instance that hasn't yet been presented. So isBeingPresented will always be false. Besides that, that property can only be used from within a view controller's view[Will|Did]Appear method.
What you want to check is to see if the tabBarController has already presented another view controller or not.
And lastly, only create and setup the sign-in view controller if it should be presented.
let appDelegate = UIKit.UIApplication.shared.delegate!
if let tabBarController = appDelegate.window?.rootViewController as? UITabBarController {
if tabBarController.presentedViewController == nil {
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let signInVC = storyboard.instantiateViewController(withIdentifier: "SignInVC") as! SignInVC
signInVC.modalPresentationStyle = UIModalPresentationStyle.formSheet
tabBarController.present(signInVC, animated: true, completion: nil)
}
}

Getting Black Screen with navigationController?.pushViewController (Swift 3.0)

I am having problems programmatically sending a user from one view controller to another. I am posting the code associated with he flow below. In addition to letting me know what the correct code is (which I would appreciate) I'd also be interested if the logic/design itself seems OK.
I am controlling my UI programmatically. Accordingly, in my app delegate didfinnishlaunchingwithoptions I have
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
FIRApp.configure()
window?.rootViewController = SigninController()
When the user opens the app, they are redirected to the SigninController.
Then, inside of SigninController, I am handling all of the social authentication stuff against Firebase. I have a listener in my code to confirm that the user is (or is not) authenticated and sends him along:
let provider: [FUIAuthProvider] = [FUIGoogleAuth(), FUIFacebookAuth()]
FUIAuth.defaultAuthUI()?.providers = provider
// listen for changes in the authorization state
_authHandle = FIRAuth.auth()?.addStateDidChangeListener { (auth: FIRAuth, user: FIRUser?) in
// check if there is a current user
if let activeUser = user {
// check if the current app user is the current FIRUser
if self.user != activeUser {
self.user = activeUser
self.signedInStatus(isSignedIn: true)
print("user session is active, redirecting...")
let nextViewController = CustomTabBarController()
self.navigationController?.pushViewController(nextViewController, animated: true)
}
} else {
// user must sign in
self.signedInStatus(isSignedIn: false)
self.loginSession()
}
}
}
In the above code, if the user is confirmed as signed in, then I use the below code to send them along. This is where I am having the problem. Right now I just see a black screen but no error message.
let nextViewController = CustomTabBarController()
self.navigationController?.pushViewController(nextViewController, animated: true)
And here is the code for the CUstomTabBarController class.
class CustomTabBarController : UITabBarController
{
override func viewDidLoad()
{
super.viewDidLoad()
let home = createNavController(imageName: "gen-home", rootViewController: HomeController(collectionViewLayout: UICollectionViewFlowLayout()))
let loc = createNavController(imageName: "loc-map-route", rootViewController: LocController(collectionViewLayout: UICollectionViewFlowLayout()))
let stats = createNavController(imageName: "pre-bar-chart", rootViewController: StatsController(collectionViewLayout: UICollectionViewFlowLayout()))
let profile = createNavController(imageName: "account", rootViewController: ProfileController(collectionViewLayout: UICollectionViewFlowLayout()))
viewControllers = [home, loc, stats, profile]
}
private func createNavController(imageName: String, rootViewController: UIViewController) -> UINavigationController
{
let navController = UINavigationController(rootViewController: rootViewController)
navController.tabBarItem.image = UIImage(named: imageName)
return navController
}
}
I am sure I am overlooking something silly, but sometimes it takes another pair of eyes to point it out.
Thanks in advance.
Here
self.navigationController?.pushViewController(nextViewController, animated: true)
there is no navigationController.
In your AppDelegate you need to do this:
let rootViewController = SigninController()
let navController: UINavigationController = UINavigationController(rootViewController: rootViewController)
FIRApp.configure()
self.window?.rootViewController = navController
self.window?.makeKeyAndVisible()
It's better to assign rootViewController to the window before making it visible. Otherwise there may be screen blinks.

Setting rootViewController then navigate to next view programatically

Initially I have a hierarchy below after login
-> MyCoursesViewController
-> CourseInfo UITabBarController
If the user closes the app, then re-enters, the rootViewController will be the CourseInfo UITabBarController which is correct. However when the user needs to view a different course (exits the course), they can’t go ‘back’ to MyCoursesViewController because its no longer on the stack.
In AppDelegate:
if (inCourse) {
let storyboard : UIStoryboard = UIStoryboard(name: “Main”, bundle: nil)
let courseInfoTabController = storyboard.instantiateViewControllerWithIdentifier(“CourseInfo”) as! UITabBarController
self.window?.rootViewController = courseInfoTabController
} else {
let storyboard : UIStoryboard = UIStoryboard(name: “Main”, bundle: nil)
let myCoursesViewController = storyboard.instantiateViewControllerWithIdentifier(“MyCourses”)
self.window?.rootViewController = myCoursesViewController
}
Is there some way I can put the MyCoursesViewController as the rootViewController then automatically navigate to Course Info UITabBarController just so the MyCoursesViewController is on the hierarchy incase they hit back (exits the course)?
Alternatively is it better if the user exits the course (hit back), we delete the rootViewController somehow and replace with a new rootViewController? Another option is if we just replace the rootViewController, will the old one be freed from memory or is it still referenced somewhere?
e.g.
CourseInfo UITabBarController is currently still rootViewController but now we swap it out with a new one
let mainStoryBoard = UIStoryboard(name: "Main", bundle: nil)
let myCoursesViewController = mainStoryBoard.instantiateViewControllerWithIdentifier(“MyCourses”) as! ViewController
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = myCoursesViewController
In your AppDelegate you can set your hierarchy. Try with something like:
let storyboard : UIStoryboard = UIStoryboard(name: “Main”, bundle: nil)
let myCoursesViewController = storyboard.instantiateViewControllerWithIdentifier(“MyCourses”)
if isInCourse{
let courseInfoTabController = storyboard.instantiateViewControllerWithIdentifier(“CourseInfo”) as! UITabBarController
let navigationBar = UINavigationController()
navigationBar.setViewControllers([myCoursesViewController,courseInfoTabController], animated: false)
self.window?.rootViewController = navigationBar
}else{
self.window.rootViewController = myCoursesViewController
}

Resources