Get current ViewController in external method in Swift - ios

I'm making a external function to check if user is already logged into Firebase.
My code is working, but in trying to ensure that current VC is dismissing in the end, I get an error.
My question is: How can i get the current VC or how can i use self. to reference the current VC that function is called?
class Helper{
static func checkIfLogged() {
Auth.auth().addStateDidChangeListener { auth, user in
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "HomeViewController")
UIApplication.shared.keyWindow?.rootViewController = controller
self.dismiss(animated: true, completion: nil) **** ERROR IS HERE ***
}
}
}

extension UIApplication{
class func getPresentedViewController() -> UIViewController? {
var presentViewController = UIApplication.shared.keyWindow?.rootViewController
while let pVC = presentViewController?.presentedViewController
{
presentViewController = pVC
}
return presentViewController
}
}
just add this extension and call : UIApplication. getPresentedViewController()

Related

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

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!

Swift 3 - Close current ViewController and Open new ViewController

I've gone through a tad amount of SO posts about this but none of them clearly state how to get this done in Swift 3.
Simply put, I need to close the current view that is displayed and open a new view controller. Such that the only view controller open would be the 2nd VC
This is the code I tried using, but when I try this the view controller gets closed without the rest of the code getting executed
//Close current VC
self.dismiss(animated: true, completion: nil)
//Open the new VC
let mainStoryboard: UIStoryboard = UIStoryboard(name:"Main",bundle:Bundle.main)
let secondViewController: SecondViewController = mainStoryboard.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
self.present(secondViewController, animated: true, completion: nil)
Can someone help me out? I just need a simple transition from 1st VC to 2nd VC and close the 1st VC.
The problem is, that "self" can not present the new ViewController since it is the ViewController that is getting dismissed.
You could try calling self.presentingViewController?.present(secondViewController, animated: true, completion: nil)
After spend full day i got a solution on Apple' developer website (on this link)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let secondVC = storyboard.instantiateViewController(identifier: "second_view_controller")
secondVC.modalPresentationStyle = .fullScreen
secondVC.modalTransitionStyle = .crossDissolve
present(secondVC, animated: true, completion: nil)
If you presented that view controller from some view controller or it is the viewcontroller from navigation or tab viewcontroller, then try to get last visible view controller, code is in this link
Get the current displaying UIViewController on the screen in AppDelegate.m
or else use this code (I have copied from that link)
public extension UIWindow {
public var visibleViewController: UIViewController? {
return UIWindow.getVisibleViewControllerFrom(self.rootViewController)
}
public static func getVisibleViewControllerFrom(_ vc: UIViewController?) -> UIViewController? {
if let nc = vc as? UINavigationController {
return UIWindow.getVisibleViewControllerFrom(nc.visibleViewController)
} else if let tc = vc as? UITabBarController {
return UIWindow.getVisibleViewControllerFrom(tc.selectedViewController)
} else {
if let pvc = vc?.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(pvc)
} else {
return vc
}
}
}
}
Then present your viewcontroller from that visibleViewController -
//Close current VC
self.dismiss(animated: true, completion: nil)
//Open the new VC
let mainStoryboard: UIStoryboard = UIStoryboard(name:"Main",bundle:Bundle.main)
let secondViewController: SecondViewController = mainStoryboard.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
window?.visibleViewController?.present(secondViewController, animated: true, completion: nil)
If your view controller is the root view controller, then set it to root view controller
if let delegate = UIApplication.shared.delegate as? AppDelegate {
delegate.window?.rootViewController = secondViewController
}

How to push and present to UIViewController programmatically without segue in iOS Swift 3

I am using this code for push SHOW and MODALLY programmatically in iOS Objective C.
And now want to know about Swift 3.
NewsDetailsViewController *vc = instantiateViewControllerWithIdentifier:#"NewsDetailsVCID"];
vc.newsObj = newsObj;
//--this(SHOW)
[self.navigationController pushViewController:vc animated:YES];
//-- or this(MODAL)
[self presentViewController:vc animated:YES completion:nil];
Push
do like
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("NewsDetailsVCID") as NewsDetailsViewController
vc.newsObj = newsObj
navigationController?.pushViewController(vc,
animated: true)
or safer
if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NewsDetailsVCID") as? NewsDetailsViewController {
viewController.newsObj = newsObj
if let navigator = navigationController {
navigator.pushViewController(viewController, animated: true)
}
}
present
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("NewsDetailsVCID") as! NewsDetailsViewController
vc.newsObj = newsObj
present(vc!, animated: true, completion: nil)
or safer
if let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NewsDetailsVCID") as? NewsDetailsViewController
{
vc.newsObj = newsObj
present(vc, animated: true, completion: nil)
}
With an elegant way.
Create an Navigatable protocol:
protocol Navigatable {
/// Storyboard name where this view controller exists.
static var storyboardName: String { get }
/// Storyboard Id of this view controller.
static var storyboardId: String { get }
/// Returns a new instance created from Storyboard identifiers.
static func instantiateFromStoryboard() -> Self
}
Create a default instantiate controller implementation:
/**
Extension of Navigatable protocol with default implementations.
*/
extension Navigatable {
static func instantiateFromStoryboard() -> Self {
let storyboard = UIStoryboard(name: self.storyboardName, bundle: nil)
guard
let viewController = storyboard
.instantiateViewController(withIdentifier: self.storyboardId) as? Self else {
fatalError("Cannot instantiate the controller.")
}
return viewController
}
}
Extends the UIViewController to push a view controller:
extension UIViewController {
/**
Pushes a view controller of the provided type.
- Parameter viewControllerType: Type of view controller to push.
- Parameter completion: Function to be executed on completion.
Contains the view controller that was pushed when successful and nil otherwise.
*/
func pushViewControllerOfType<T: Navigatable>(viewControllerType: T.Type, completion: (T) -> Void) {
let viewController = T.instantiateFromStoryboard()
if let vc = viewController as? UIViewController {
self.pushViewController(vc, animated: true)
}
completion(viewController)
}
/**
Pushes a view controller of the provided type.
- Parameter viewControllerType: Type of view controller to push.
*/
func pushViewControllerOfType<T: Navigatable>(viewControllerType: T.Type) {
self.pushViewControllerOfType(viewControllerType: viewControllerType) { _ in }
}
}
Then you can use the Navigatable protocol for a specific view controller.
class MySuperViewController {
override func viewDidLoad() {
...
}
// ...
}
extension MySuperViewController: Navigatable {
static var storyboardName: String {
return "Main"
}
static var storyboardId: String {
return "MySuperViewControllerId" // From your story board name Main
}
}
// Instantiate your controller
let vc = MySuperViewController.instantiateFromStoryboard()
// Or
//
// Push your view controller
// testViewController.swift
self.pushViewControllerOfType(viewControllerType: MySuperViewController)
//Create object of view controller
let obj = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllerIdentifier”) as! ViewControllerName
//Push Controller
self.navigationController?.pushViewController(obj, animated: true)
//Present Controller
self.navigationController?.present(obj, animated: true, completion: nil)

using presentviewcontroller inside a function swift

I am trying to use this inside my function to present a view controller after a function runs, so included it inside
let window = UIWindow()
let rootViewController = window.rootViewController
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let setViewController = mainStoryboard.instantiateViewControllerWithIdentifier("EmployeeTBCIdentifier")
rootViewController?.presentViewController(setViewController, animated: true, completion: nil)
It's not doing anything, it works if I use it inside viewDidLoad()
here's the main idea, and would it be possible to use completions according to this example,
func logInType(completion(Void1, Void2)->()) {
if login == employer{
void1
}else if login == employee{
void2
}
}
when using the function I want to return this
func logInType{(void1, void2) -> void in
void1 = self.presentview //I want to push view to another if this code executed
void2 = self.presentview //same idea
}
updated : code is working but it's not pushing to the viewcontroller identified.
dispatch_async(dispatch_get_main_queue()){
let window = UIWindow()
let rootViewController = window.rootViewController
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let setViewController = mainStoryboard.instantiateViewControllerWithIdentifier("EmployeeTBCIdentifier")
rootViewController?.presentViewController(setViewController, animated: true, completion: nil)
}
Seems to me that there might be a problem in how you are getting the rootViewController. I think the let window = UIWindow() call is going to return a new UIWindow object instead of what you want which is the window that the root view controller (and your app) is associated with. Continuing this, rootViewController will be nil and your optional chain call rootViewController?.presentViewController(setViewController, animated: true, completion: nil) won't do anything. Use an explicit unwrap via rootViewController!.presentViewController(setViewController, animated: true, completion: nil) and you should see the error
To rectify, get the correct UIWindow another way, either from the UIAppDelegate or from another view controller using OtherViewController.view!.window!
Try using it like so:
dispatch_async(dispatch_get_main_queue())
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("mainView") as! MainViewController
self.presentViewController(vc, animated: true, completion: nil)
}

Resources