Can't dismiss UIViewController after Successful Login - ios

I'm struggling to dismiss a UIViewController from the NavigationStack after login is completed.
The login screen is a UIViewController is presented with this line of code
let loginController = LoginController()
self.present(loginController, animated: true, completion: nil)
And then I run this code to log the user in via firebase.
Auth.auth().signIn(withEmail: email, password: password) { (user, err) in
if let err = err {
print("Failed to sign in user with email", err )
}
//self.dismiss(animated: true, completion: nil)
let userProfileVC = UserProfileController()
let navController = UINavigationController(rootViewController: userProfileVC)
self.navigationController?.pushViewController(navController, animated: true)
}
As you can see I have tried the pushViewController method, and have also tried the commented self.dismiss method? Nothing I do seems to remove the loginController UIView and take me back to the UINavigationController home screen. Can anyone help me out, thanks a lot.

you can do as this
self.dismiss(animated: true, completion: {
let userProfileVC = UserProfileController()
let navController = UINavigationController(rootViewController: userProfileVC)
self.navigationController?.pushViewController(navController, animated: true)
})

You might have to print your viewstack and show it to us in order to understand better but try one of these methods:
navigationController?.popViewController(animated: true)
or
navigationController?.popToRootViewController(animated: true)
The first method will dispose of the most recently pushed view controller, while the second will remove all but the navigation controllers "home screen" which you seek.

You can not push to particular controller while any controller is present on current controller. so you can use protocol delegate on controller which is present current controller. when delegate method called then you can push to your controller.

Related

Swift iOS - how to properly use popToViewController

it's again me - learning swift.
Question is simple, i have control views like this:
[Initial]->[NotLogged]->[SignUp]
Now after sign up i have double dismiss, it's really ugly!
I wan't to go straight from SignUp to Initial page.
I tried this code but sadly it closing app without any error.
self.dismiss(animated: true, completion: {
let controllers = self.navigationController?.viewControllers
for vc in controllers! {
if vc is InitialViewController {
_ = self.navigationController?.popToViewController(vc as! InitialViewController, animated: true)
}
}
} )
pushViewController works same as above, there is output:
2020-01-29 20:30:38.342180+0100 BillyBill[44355:19540995] Can't end
BackgroundTask: no background task exists with identifier 10 (0xa), or
it may have already been ended. Break in
UIApplicationEndBackgroundTaskError() to debug.
Simply just pop to root view controller
self.navigationController?.popToRootViewController(animated: true)
You can just use self.navigationController?.popToRootViewController() command in place of
for vc in controllers! {
if vc is InitialViewController {
_ = self.navigationController?.popToViewController(vc as! InitialViewController, animated: true)
}
}

How to solve this error "Warning:Attempt to present whose view is not in the window hirearchy"?

I'm developing an iOS application but I don't have storyboard and I do pure swift code, when I try to check the authentication in the MainViewController and I use perform to go to another ViewController and if token doesn't exist show button in MainViewController I ran into this warning and it won't work.
when I use perform like the snippet below to go to another ViewController by clicking on a button it works just fine.
I've seen all the answer by the title I'm asking here but all the examples has storyboards so it's not related to my question here.
here's a snippet that I'm trying to do in my app.
if defaults.string(forKey: Constants().userTokenKey) != nil
&& defaults.string(forKey: Constants().userTokenKey) != "" {
print("YOU ARE IN ELSE!")
let vc = SelectLocationOnMapViewController()
UIApplication.topViewController()?.present(vc, animated: true, completion: nil)
} else {
UIView.animate(withDuration: 1, animations: {
self.loginRegisterParentView.alpha = 1.0
})
setButtonActions()
}
The problem is here
UIApplication.topViewController()?.present(vc, animated: true, completion: nil)
it seems you use the rootVC which isn't yet fully presented to present another Vc , you need to move this code from viewDidLoad to say viewWillAppear/viewDidAppear

Swift ios error in navigating back

I have use the following code below . to navigate but i have tried all possible idea i have to navigate back but i always received an error. like Warning: Attempt to present on whose view is not in the window . when using the code below what is the best way to navigate back ? or to go to another view?
if let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ScoreViewController") as? ScoreViewController
{
present(vc, animated: true, completion: nil)
}
If you are using NavigationController to push one ViewController to other then for navigate back you have to use Pop like this,
self.navigationController?.popViewController(animated: true)
and if you are using Modal Controller to present another Controller then for navigate back you have to use dismiss like this,
dismiss(animated: true, completion: nil)
Let me know in case of any queries.
You are presenting the view controller not pushing it. To come back to the controller, write down the below controller:-
dismiss(animated: true, completion: nil)

How do I prevent a navigationController from returning to root when dismissing MFMailComposeViewController

When I dismiss an instance of MFMailComposeViewController or MFMessageComposeViewController that is presented modally from the third viewController in a navigation stack, the navigation stack is reset, and the root VC is reloaded. How can I prevent this behavior and remain on the original presenting viewController (third VC in the stack)? I get the same behavior whether I call dismiss from the presenting VC, the presented VC, or the navigationController.
This has been asked before, but I have not seen a solution.
App Structure looks like this:
TabBarController
Tab 1 - TripsNavController
-> Trips IntroductionVC (root VC) segue to:
-> TripsTableViewController segue to:
-> TripEditorContainerVC
- TripEditorVC (child of ContainerVC)
- HelpVC (child of ContainerVC)
Tab 2...
Tab 3...
Tab 4...
In the TripEditorVC I present the MFMailComposeViewController. The functions below are declared in an extension to UIViewController that adopts the MFMailComposeViewControllerDelegate protocol
func shareWithEmail(message: NSAttributedString) {
guard MFMailComposeViewController.canSendMail() else {
showServiceError(message: "Email Services are not available")
return
}
let composeVC = MFMailComposeViewController()
composeVC.setSubject("My Trip Plan")
composeVC.setMessageBody(getHTMLforAttributedString(attrStr: message), isHTML: true)
composeVC.mailComposeDelegate = self
present(composeVC, animated: true, completion: nil)
}
Then in the delegate method I dismiss the MFMailComposeVC:
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
switch result {
case .sent:
print("Mail sent")
case .saved:
print("Mail saved")
case .cancelled:
print("Mail cancelled")
case .failed:
print("Send mail failed")
}
if error != nil {
showServiceError(message: "Error: \(error!.localizedDescription)")
}
dismiss(animated: true, completion: nil)
}
I have tried the following to present and dismiss and get the same behavior, i.e.: the TripsNavController clears the nav stack and reloads the TripsIntroductionVC as its root VC:
self.present(composeVC, animated: true, completion: nil)
self.parent?.present(composeVC, animated: true, completion: nil)
self.parent?.navigationController?.present(composeVC, animated: true, completion: nil)
self.navigationController?.present(composeVC, animated: true, completion: nil)
You can also check with presentingViewController?.dismiss method to get the solution.
I have tried with following navigation stack.
I am able to send email successfully from Container VC's Send Email button using your code only.
Can you please check and verify navigation flow?
Please let me know if you still face any issue.
dismiss(animated: true, completion: nil)
to
self.dismiss(animated: true, completion: nil)
Try this
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let secondViewController = storyboard.instantiateViewControllerWithIdentifier("secondViewControllerId") as! SecondViewController
self.presentViewController(secondViewController, animated: true, completion: nil)
or
https://stackoverflow.com/a/37740006/8196100
Just Use Unwind Segue its Pretty simple and perfect in your case
I have used many times..
just look at this Unwind segue's example
. If u still don't able to find answer or not able to understand the Unwind segue then just reply to me.
Hope this will solve your problem.
I found the problem with my navigation stack today. I built a simple
project that duplicated the tabBarController/NavigationControler architecture of my problem project, component by component, until dismissing a MFMailComposeViewController caused my navigation stack to reset as described in my original post.
That immediately pointed to the source of the bug. In my subclassed UINavigationCotroller I was instantiating the root viewController in code so that I could skip an introductory view if the user had set a switch in the apps settings. In order to pick up changes in that switch setting I was calling my instantiation code in viewDidAppear of the navigationController. That worked fine EXCEPT when dismissing the mailComposeVC. The fix was to add a guard statement in viewDidAppear to return if the navControllers viewController collection was not empty, and send and respond to an NSNotification when the switch was changed.
class TopNavigationController: UINavigationController {
var sectionType: SectionType?
var defaults = UserDefaults.standard
var showIntroFlag: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
// Handle initial load of the tab bar controller where we are not sent a sectionType
if sectionType == nil {
sectionType = .groups
}
setShowIntroFlag()
NotificationCenter.default.addObserver(self, selector: #selector(resetControllers), name: NSNotification.Name(rawValue: "kUserDidChangeShowIntros"), object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
guard self.viewControllers.isEmpty else {
return
}
loadControllers()
}
func setShowIntroFlag() {
showIntroFlag = true
// Check NSUserDefaults to see if we should hide the Intro Views for all sections
if defaults.bool(forKey: "SHOW_SECTION_INTROS") == false {
showIntroFlag = false
}
}
func loadControllers() {
if showIntroFlag == true {
showIntro()
} else {
skipIntro()
}
}
func resetControllers() {
setShowIntroFlag()
loadControllers()
}

How to present different view controllers from appdelegate in ios

Actually I am having navigationcontroller as root controller it is embed in main.storyboard,i am having two screens one screen login and another one home as per login credentials i need to skip login screen and i need to show home screen.From appdelegate i am doing this skipping it is not working properly
Unbalanced calls to begin/end appearance transitions for <UINavigationController: 0x7fadf384c600>.
let storyboard=UIStoryboard.init(name: "Main", bundle: nil)
let navigationController=storyboard.instantiateInitialViewController()
let username=UserDefaultUtil.getString(key: AppConstants.PREF_USERID)
print(username!)
if username != ""
{
window?.rootViewController=navigationController
let sectionController=SectionController(nibName: "SectionController" , bundle: nil)
navigationController?.present(sectionController, animated: true, completion: nil)
}
I guess you are trying to present your sectionController in navigationController, its not really how it works, try this code:
let navigationController = self.storyboard?.instantiateInitialViewController() as! UINavigationController
and replace the present with this:
navigationController.setViewControllers([sectionController], animated: false)
or just drop the navigationController instantiate and create it with code and set it as window?.rootViewController:
let sectionController=SectionController(nibName: "SectionController" , bundle: nil)
let nav = UINavigationController(rootViewController: sectionController)
window?.rootViewController = nav
First, check the user credentials in the login page. Then use:
if hasCredentials {
let vc:AnyObject! = self.storyboard?.instantiateViewController(withIdentifier: "someViewController")
self.show(vc as! UIViewController, sender: vc)
}
Sidenote: Personally, I do this from the login page because it simplifies the process and I do not like having weight sitting in my AppDelegate. If you were thinking you did not want people seeing your login screen who are already members, you can do it from an AppDelegate, but take into account the user experience might be diminished during the loading process if this is the route you decide to take.

Resources