Swift - pushing ViewController not working after presenting ViewController again - ios

The question sounds a bit confusing but I don't know how to describe it better.. Let me explain:
The first ViewController that gets presented is FirstLaunchVC, where he user types in his email and if he is registered he get's to LoginVC and from there he get's to MainVC. Everything is working fine.
In MainVC the user can sign out and get's back to FirstLaunchVC. However, after doing the weiterButton which should bring the user to LoginVC is not doing anything.
FirstLaunchVC:
#objc func weiterButtonTapped() {
email = emailTextfield.text!.trimmingCharacters(in: .whitespacesAndNewlines)
//prüfen, ob Email schon registriert ist
Auth.auth().fetchSignInMethods(forEmail: email) { (methods, error) in
//Email ist noch nicht registriert -> sign up
if methods == nil {
let SignUpView = self.storyboard?.instantiateViewController(withIdentifier: "SignUpVC") as! SignUpViewController
SignUpView.email = self.email
self.navigationController?.pushViewController(SignUpView, animated: false)
}
//Email ist registriert -> login
else {
print("hi")
self.view.endEditing(true)
let LoginView = self.storyboard?.instantiateViewController(withIdentifier: "LoginVC") as! LoginViewController
LoginView.email = self.email
self.navigationController?.pushViewController(LoginView, animated: true)
}
}
}
Main Problem:
The print(hi) is printing but pushViewController is not working after signing out.
LoginVC:
func transitionToHome () {
let homeVC =
storyboard?.instantiateViewController(withIdentifier: Constants.Storyboard.homeViewController) as? MainViewController
let navigationController = UINavigationController(rootViewController: homeVC!)
view.window?.rootViewController = navigationController
view.window?.makeKeyAndVisible()
}
MainVC:
This is where the user can sign out.
#objc func signoutButtonTapped() {
UserDefaults.standard.setIsLoggedIn(value: false)
UserDefaults.standard.synchronize()
let firstLaunchVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "FirstLaunchVC")
self.navigationController?.present(firstLaunchVC, animated: true)
}
I tried to explain the problem the best I can. If anything is still unclear just let me know. I am happy for any help :)

if after sign out weiterButtonTapped() called,
you should put your pushController line in dispatch that's because controller be fully deallocated:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.navigationController?.pushViewController(LoginView, animated: true)
}

Related

Dismiss and popViewController not working when presentingViewController is UITabbarController

My scence
UITabbar ( UINavigation(page1) -push-> page2 -push-> page3 -present-> page4 )
From my feature I stay on page4 and want to dismiss it and pop to root ( page1 ) but when I get presentingViewController it return UITabbarController not my UINavigationBarController
My code now.
self.dismiss(animated: true) { [weak self] in
guard let topVc = self?.presentingViewController else { return }
if let destinationVC = topVc.navigationController?.viewControllers.filter({$0 is Page1ViewController}).first {
topVc.navigationController?.popToViewController(destinationVC, animated: true)
}
}
My code working before I add UITabbar to my flow.
Can someone help me or explain me about root cause ? Thank you for help.
Updated
Now I found a solution about this but I think it not the good way enough.
self.viewController.dismiss(animated: true) {
guard let topVc = Tools.getTopViewController(),
let tabBarVc = topVc as? MainTabBarController,
let nav = tabBarVc.selectedViewController as? BaseNavigationBarController else { return }
if let destinationVC = nav.viewControllers.filter({$0 is WalletViewController}).first {
nav.popToViewController(destinationVC, animated: true)
}
}

How to pass UIVIewController name as a parameter to particular function using swift?

In my scenario, I need to pass UIVIewController name and some more string values to particular function. I tried below code but not getting result.
Passing Parameters To Particular Function
self.accountoptionscall(vcName: UIViewController(), vcIdentifier: "profileviewcontroller", popUpVC: ProfileViewController.self)
func accountoptionscall<T: UIViewController>(vcName: UIViewController,vcIdentifier: String, popUpVC: T.self) {
let viewcontrollers = self.storyboard!.instantiateViewController(withIdentifier: vcIdentifier) as! vcName
let navController = UINavigationController(rootViewController: viewcontrollers)
self.present(navController, animated:true, completion: nil)
}
I use it in my app, I think it can help you.
extension UIViewController {
/// Load UIViewController type from UIStoryboard
class func loadFromStoryboard<T: UIViewController>() -> T {
let name = String(describing: T.self)
let storybord = UIStoryboard(name: name, bundle: nil)
if let viewController = storybord.instantiateInitialViewController() as? T {
return viewController
} else {
fatalError("Error: No initial view controller in \(name) storyboard!")
}
}
}
Here how you can use it:
func loadVC<T: UIViewController>(controller: T) {
let vc: T = T.loadFromStoryboard()
let navigationVC = UINavigationController(rootViewController: vc)
self.present(navController, animated:true, completion: nil)
// or if use in appDelegate you can do it: window?.rootViewController = navigationVC
}

Swift 4 – Custom Alerts with protocols

I'm having issues with Custom alerts and sending actions back to VC from which alert was called.
I have two classes:
Factory
ConfirmationAllert
User journey I'm trying to achieve:
The user performs actions in the Factory class after he finishes I call ConfirmationAllert using such code:
func showAlert() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myAlert = storyboard.instantiateViewController(withIdentifier: "ConfirmationAllert")
myAlert.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
myAlert.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
(view as? UIViewController)?.present(myAlert, animated: true, completion: nil)
}
In ConfirmationAllert class I have button, which:
dismisses alert
sends action to Factory - this action is to dismiss Factory VC and go back to previous VC.
First action completes successfully, the second action not working. I'm using protocols to send the second action to Factory VC, but something is not working, and I don't know what.
Here is my code:
Factory
final class FactoryViewController: UIViewController {
let alert = ConfirmationAllert()
#IBAction func didPressSave(_ sender: UIButton) {
showAlert()
}
func goToPreviousVc() {
alert.delegate = self
print("Inside factory") -> print don't get called
// navigationController?.popViewController(animated: true) -> none of this works
// dismiss(animated: true, completion: nil) -> none of this works
}
}
extension FactoryViewController: ConfirmationAllertDelegate {
func dismissVC() {
goToPreviousVc()
print("Go to previous")
}
}
ConfirmationAllert
protocol ConfirmationAllertDelegate {
func dismissVC()
}
class ConfirmationAllert: UIViewController {
var delegate: ConfirmationAllertDelegate?
#IBAction func didPressOk(_ sender: UIButton) {
self.delegate?.dismissVC()
}
}
I didn't include viewDidLoad methods as I'm not calling anything there.
My issue is that method goToPreviousVc() doesn't perform any actions.
Thank you in advance for your help!
I guess your problem is that you setup your ConfirmationAllertDelegate at goToPreviousVc that supposed to be called using that delegate.
Instead, try to set up you delegate when you creating myAlert object
let myAlert = storyboard.instantiateViewController(withIdentifier: "ConfirmationAllert")
(myAlert as? ConfirmationAllert).delegate = self
// the rest of your code
After that, your alert will have a delegate since it was created and when you press the button, it should work as you expect.
Try to use below code
func showAlert() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myAlert = storyboard.instantiateViewController(withIdentifier: "ConfirmationAllert") as! ConfirmationAllert
myAlert.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
myAlert.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
myAlert.delegate = self
present(myAlert, animated: true, completion: nil)
}

Dissmiss from UITabBarController

I guess it can be duplicated, but I looking everywhere and didn't find a solution for me.
So about my question. I have something like this
open this image to see more
In my AppDelegate I have func
func logIn() {
let userExist = UserDefaults.standard.string(forKey: "auth_key_user")
if userExist != nil && userExist != "" {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let whereToGo = storyboard.instantiateViewController(withIdentifier: "AllPostsOfUserTabBarController") as! AllPostsOfUserTabBarController
window?.rootViewController = whereToGo
}
}
If user exist it lead me to first view controller inside tab bar controller. There I have navigation button with action to logout.
I need log out(send data to the server) and then go to my first view controller with text field and button where I can again log in.
How do I need to implement it?
Try this code after logout-
DispatchQueue.main.sync() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = storyboard.instantiateInitialViewController()
appDelegate.window?.rootViewController = loginVC
}
Hope it helps!
Put this code in function of your logout button. It will throw you back to your root controller.
func logoutBtnClicked()
{
DispatchQueue.main.sync()
{
(send your data to server)
self.navigationController?.popToRootViewController(animated: true)
}
}
Try this in appdelegate
While Login
self.window?.rootViewController = whereToGo
While Logout
func setupLoginViewController() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC: UIViewController? = storyboard.instantiateViewController(withIdentifier: "loginVCID") as? UIViewController
let navController: UINavigationController? = UINavigationController(rootViewController: loginVC!) as UINavigationController
window?.rootViewController = navController
}

Viewcontroller just shows black screen after implementing a rootviewcontroller

I am pretty new to programming, thats why I can't really figure out how to solve this issue.
I have implemented a rootviewcotnroller in the app delegate so that if the user is logged in he is pushed directly to the app content instead of the login view controller.
However it doesn't really work. As is already said I added the following code to the app delegate:
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = MainViewController()
The MainViewcontroller is set up like this:
class MainViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
if isLoggedIn() {
let homeController = UserViewController()
viewControllers = [homeController]
}else {
perform(#selector(showLoginController), with: nil, afterDelay: 0.01)
}
}
fileprivate func isLoggedIn() -> Bool {
return UserDefaults.standard.isLoggedIn()
}
func showLoginController() {
let loginController = LoginViewController()
present(loginController, animated: true, completion: {
})
}
}
To the Userviewcontroller I have added the following lines:
func handleSignout() {
UserDefaults.standard.setisLoggedIn(value: false)
print("is logged out")
}
#IBAction func SignOut(_ sender: Any) {
handleSignout()
if FIRAuth.auth()!.currentUser != nil {
do {
try? FIRAuth.auth()?.signOut()
if FIRAuth.auth()?.currentUser == nil {
let loginViewViewcontroller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Login") as! LoginViewController
self.present(loginViewViewcontroller, animated: true, completion: nil)
}
}
}
}
Then I have created an extension with the UserDefaults to save the boolean Value whether the user is logged in or logged out:
extension UserDefaults {
enum UserDefaultKeys: String {
case isLoggedIn
}
func setisLoggedIn(value: Bool) {
set(false, forKey: UserDefaultKeys.isLoggedIn.rawValue)
synchronize()
}
func isLoggedIn() -> Bool {
return bool(forKey: UserDefaultKeys.isLoggedIn.rawValue)
}
}
In the LoginviewController, which just shows a black screen if shown at first sight, I have added :
func finishLoggingIn() {
let rootViewController = UIApplication.shared.keyWindow?.rootViewController
guard let MainNavigationController = rootViewController as? MainViewController else {return}
MainNavigationController.viewControllers = [UserViewController()]
print("is logged in")
UserDefaults.standard.setisLoggedIn(value: true)
dismiss(animated: true, completion: nil)
}
This function is called when user pushes the login button.
The app recognizes if the user is logged or not, but it doesn't matter if the user is logged in or not, that first view controller which is presented shows a black screen, which is most likely the loginviewcontroller but if the user is logged in the userviewcontroller shows a black screen as well if is the first view controller to be presented. ...
Has anybody an idea why that is?
I have figured out how to solve it. It is way easier then I expected it to be. Maybe the solution I've found is not the best way of doing it, but as long as it works I am happy!
I deleted all function with the UserDefaults.
To my MainViewController I have just added a the following code, which is suggested by Firebase itself:
import UIKit
import FirebaseAuth
// DIeser Viewcontroller wird immer als erstes aufgerufen und entscheidet, ob der loginviewcontroller gestartet wird oder der UserViewController gestartet wird.
class MainViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
FIRAuth.auth()?.addStateDidChangeListener() { (auth, user) in
if user != nil {
let LoginVc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "usersVC")
self.present(LoginVc, animated: true, completion: nil)
}else {
let UserVc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginVc")
self.present(UserVc, animated: true, completion: nil)
}
}
}
}
The MainViewController is still set as the rootviewcontroller in the AppDelegate.
I hope I helps someone else in the future!

Resources