opening a tab bar controller on successful login swift - ios

I am extremely new to swift and storyboards. I have an initial view set which presents the user with login or register options. on the success of my login web service, I am trying to open a tab bar. I am getting into the success of the webservice as I can see the response.
My code for attempting to load the tab bar is as folllows in my initial view controller:
func loadHomeScreen()
{
emailField.text = ""
passwordField.text = ""
self.presentViewController(UIStoryboard.tabbarController()!, animated: true, completion: nil)
}
And at the very bottom of that file, I have the following:
private extension UIStoryboard {
class func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()) }
class func tabbarController() -> TabbarController? {
return mainStoryboard().instantiateViewControllerWithIdentifier("TabbarControllerID") as? TabbarController
}
}
And in my storyboard I have given the tabbarcontroller the id above. When I run the app (tested on the simulator for iphone6), I am getting the error 'found nil while unwrapping an Optional value' and this is pointing to the line of code in my loadHome func above (self.presentViewController(UIStoryboard.tabbarController()!, animated: true, completion: nil))
Any help would be appreciated

You could instead instantiate your storyboard like so and the code would look like this under loadHomeScreen():
emailField.text = ""
passwordField.text = ""
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("TabbarControllerID") as! TabbarController
self.presentViewController(vc, animated: true, completion: nil)
You may need to change the bundle to the "mainBundle" as you have in your code, but this should work.
This may not be the optimal solution if your plan is to extend UIStoryboard for instantiating your ViewControllers but I think this might be a little easier/cleaner, depending on how your project is set up.

Related

Bar item not show when I use segue programmatically

I’m building an app which has a main vc that only existing user can log in to. The main page is the initial vc so I embed in the navigation bar to it. When I open the app in the first time I can see the bar items, but when I sign out, and log in again I can’t see the bar items, someone know why? Should I add some code? or maybe change some definitions?
I’m using segue programmatically like this:
private var handle: AuthStateDidChangeListenerHandle?
handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
if user == nil{
if let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Home") as? MainVC
{
self.present(vc, animated: true, completion: nil)
}
}else{
//keep going with the code...
})
In the simulator it starts with the main vc then I sign out (pic 1), then I log in (pic 2) and get back to the main vc but now I can’t see the bar items (pic 3).
link to the app pictures
you must present your main vc in navigationcontroller
private var handle: AuthStateDidChangeListenerHandle?
handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
if user == nil{
if let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Home") as? MainVC
{
let navVc = UINavigationController(rootViewController: vc)
self.present(navVc, animated: true, completion: nil)
}
}else{
//keep going with the code...
})

Swift Multiple Storyboard - How to access specific one

I had split a couple profiles into multiple storyboards to try to help with my freeze time because I heard to many storyboards can be the cause of why Xcode would just "freeze" for up to 10 mins sometimes causing a crash in my flow of work. So, I need to access each specific storyboard, yet don't know how. This current code accesses the "Main.Storyboard" file yet, I need to call other storyboard files. Here I am checking if a specific profile is signed in and pushing them to the profile. I had all the profiles in one storyboard but now I separated them. Just need to know how to push to other storyboards. Thanks
func checkIfBusinessLoggedIn() {
Auth.auth().addStateDidChangeListener({ (auth, user) in
if (user != nil) {
Database.database().reference().child("Businesses").child((user?.uid)!).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() {
print("Business is Signed In")
let vc = self.storyboard?.instantiateViewController(withIdentifier: "Business Profile")
self.present(vc!, animated: true, completion: nil)
}
})
}
})
}
let storyboard = UIStoryboard(name: "your_storyboard_name", bundle: nil)
vc = storyboard.instantiateViewController(withIdentifier: "your_view_controller_name")
//to push that controller on the stack
self.navigationController?.pushViewController(vc, animated: true)
If your storyboard file is "Business.storyboard", "your_storyboard_name" name should be "Business".
If you want to access other storyboards you need to give that name.
self.storyboard will always point to your default storyboard. Instead do this :
let VC = UIStoryboard(name: "VCStoryBoardName", bundle: nil).instantiateViewController(withIdentifier: "InstructionScreenController") as! VC
Use below code for multipe storybord and create ENUM for storyboard name that is easy to specify storyboard name.
//MARK:- Enum_StoryBoard
enum enumStoryBoard:String {
case main = "Main"
case home = "HomeSB"
}
let storyBoard = UIStoryboard.init(name: enumStoryBoard. home.rawValue, bundle: nil)
let objLocationSearch = storyBoard.instantiateViewController(withIdentifier: "LocationSearch") as? LocationSearch
self.navigationController?.pushViewController(objLocationSearch!, animated: true)
Using enum and generic method to return the required object.
enum AppStoryboard : String {
case First, Second
var instance : UIStoryboard {
return UIStoryboard(name: self.rawValue, bundle: Bundle.main)
}
func instantiateVC<T : UIViewController>(viewControllerClass : T.Type) throws -> T {
let storyboardID = (viewControllerClass as UIViewController.Type).storyboardID
guard let viewObj = instance.instantiateViewController(withIdentifier: storyboardID) as? T else {
throw ExcpectedError.intantiationErro(msg:"ViewController with identifier \(storyboardID)")
}
return viewObj
}
}
use this code to instantiate your viewController
let second = try! AppStoryboard.Second.viewController(viewControllerClass: SecondViewController.self) self.present(second, animated: true, completion: nil)

Why are my UIBarButtons not showing in the navigation bar? (Possible iOS 11 bug?)

If I perform segues to my VC, the UIBarButtons will show. However, if I programmatically push to the view controller, then the UIBarButtons will not show. Bug is not present in iOS 9 or iOS 10.
This code exists in the first view controller that is presented when the app opens. I am checking UserDefaults to see if I should restore what we are calling an "interaction", and I am programmatically creating the stack. Because of issues I was having with restoring the application state, I have reverted to this method.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if UserDefaults.standard.bool(forKey: "ShouldRestoreInteraction") {
if
let uri = UserDefaults.standard.url(forKey: "InteractionObjectURI"),
let id = CoreDataUtils.coord.managedObjectID(forURIRepresentation: uri),
let interactionObject = CoreDataUtils.context.object(with: id) as? InteractionObject
{
let sb = UIStoryboard(name: "Interaction", bundle: nil)
let interactionCreateOrEdit = sb.instantiateViewController(withIdentifier: "InteractionUnified") as! InteractionCreateOrEdit
interactionCreateOrEdit.interaction = Interaction(object: interactionObject)
interactionCreateOrEdit.interactionObject = interactionObject
if UserDefaults.standard.bool(forKey: "RestoreToDashboard") {
// restore to dashboard
let sb = UIStoryboard(name: "MyDashboard", bundle: nil)
let dashNav = sb.instantiateInitialViewController() as! UINavigationController
let dashboardTVC = sb.instantiateViewController(withIdentifier: "DashboardTableViewController")
self.revealViewController().setFront(dashboardTVC, animated: false)
dashNav.pushViewController(dashboardTVC, animated: false)
dashNav.pushViewController(interactionCreateOrEdit, animated: false)
navigationController!.present(dashNav, animated: false, completion: nil)
} else {
print("Error restoring interaction - did not specify bottom controller in stack")
}
} else {
print("Error restoring interaction - object in core data no longer exists")
}
}
}
However, the weird thing is if I click "Debug View Hierarchy", then the UIBarButtons will display on the phone, but not in the Debug View Hierarchy. If I continue program execution, then hit Debug View Hierarchy again, the buttons will show in the DVH. Additionally, after continuing program execution, the UIBarButtons continue to show in the navigation bar.
Looks like Mr. Matt was right in his "assurance". Not sure why my code works for previous versions of iOS...anyway solution is as follows:
if UserDefaults.standard.bool(forKey: "RestoreToDashboard") {
// restore to dashboard
let sb = UIStoryboard(name: "MyDashboard", bundle: nil)
let dashNav = sb.instantiateInitialViewController() as! UINavigationController
let dashboardTVC = sb.instantiateViewController(withIdentifier:"DashboardTableViewController")
dashNav.setViewControllers([dashboardTVC, interactionCreateOrEdit], animated: false)
self.revealViewController().setFront(dashNav, animated: false)
}

"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)
}
}

Attempt to present UINavigationController whose view

AppDelegate
initialViewController = storyboard.instantiateViewControllerWithIdentifier("LoginViewController")as! UIViewController
}
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
Takes me to LoginViewController
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("CardsNavController") as? UIViewController
self.presentViewController(vc!, animated: true, completion: nil)
I click on the login button takes me to CardsViewController
func goToProfile(button: UIBarButtonItem) {
pageController.goToPreviousVC()
}
Clicking on back button which is part of the UINavigationController runs goToProfile (above) and takes me to ViewController.swift because that's where the pageController is declared.
let pageController = ViewController(transitionStyle: UIPageViewControllerTransitionStyle.Scroll, navigationOrientation: UIPageViewControllerNavigationOrientation.Horizontal, options: nil)
The error shows up here
func goToPreviousVC() {
//let currentControllers = self.navigationController?.viewControllers
if viewControllers.isEmpty {
setViewControllers([profileVC], direction: UIPageViewControllerNavigationDirection.Reverse, animated: true, completion: nil)
pageController.presentViewController(profileVC, animated: true, completion: nil)
else {
let previousVC = pageViewController(self, viewControllerBeforeViewController: viewControllers[0] as! UIViewController)!
setViewControllers([previousVC], direction: UIPageViewControllerNavigationDirection.Reverse, animated: true, completion: nil)
}
Error:
Warning: Attempt to present <UINavigationController: 0x15ce314e0> on <MyApp.ViewController: 0x15cd2c6f0> whose view is not in the window hierarchy!
Basically the flow is like this AppDelegate -> LoginViewController -> ViewController and I need to get to ProfileViewController.
ProfileView has ProfileNavController while CardsView has CardsNavController
ViewController has a storyboardID of pageController.
Both ProfileView and and CardsView are embedded within UINavigationControllers (hence the NavController extension).
The second time I run the app after a fresh install it works perfectly (all the controllers get loaded okay). Should I push viewControllers in AppDelegate?
I've checked your code using Xcode 7, which may not be ideal for resolving this issue because I had to covert your code to Swift 2.0, but here was what I found out.
ISSUE
First time opening the app, this block:
if currentUser() != nil {
initialViewController = pageController
}
else {
initialViewController = storyboard.instantiateViewControllerWithIdentifier("LoginViewController") as UIViewController
}
self.window?.rootViewController = initialViewController
Will initialize LoginViewController and make it the current window's rootViewController.
At this point there is no pageController initialized
When user taps on the button to go to the Profile screen, this method will be called
func goToProfile(button: UIBarButtonItem) {
pageController.goToPreviousVC()
}
At this point, pageController is initialized, and off course, there is NOTHING in the viewControllers array. Let's see what happen in the goToPreviousVC method:
Original method looks like this:
let nextVC = pageViewController(self, viewControllerAfterViewController: viewControllers[0] as UIViewController)!
setViewControllers([nextVC], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
One thing you can see obviously is: calling viewControllers[0] could give you a crash because viewControllers is an empty array.
If you use Swift 2.0, it doesn't even let you compile your code :)
SOLUTION
Let's go directly to the solution: Ensure that the pageController is available before trying to call it's viewControllers.
I blindly tried fixing you code in Swift 2.0 and found out that this method would work for you:
BEFORE: In LoginViewController.swift line 63
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("CardsNavController") as? UIViewController
self.presentViewController(vc!, animated: true, completion: nil)
AFTER: Let's fix it like this
let navc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("CardsNavController") as! UINavigationController
if let viewControllers = pageController.viewControllers where viewControllers.count == 0 {
pageController.setViewControllers([navc.viewControllers[0]], direction: .Forward, animated: false, completion: nil)
}
self.presentViewController(pageController, animated: true, completion: nil)
It's working well here and probably I don't need to show you how the screen transition should look like :)
In case you would like to have the fixed source code as well, please find it HERE. Basically I converted your code to Swift 2.0 and ignored unnecessary parts like Facebook authentication for faster investigation.
Good luck & Happy coding!

Resources