How to navigate from UIView to a UIView Controller using segues? - ios

Aim: To show the View Controller - 'Welcome Page' with the button "Continue Here" from the previous view controller - 'Login Page'.
I have been making this IOS app on swift 3 in Xcode 8.2 that has a login page coded programmatically and would like to transit to another page called the Welcome page that has a button on the screen in Storyboard.
I'm having problems in connecting a view controller that has been coded programmatically to another view controller containing buttons that have been dragged on the storyboard. I have read many articles and watched many tutorials on constructing a segue programmatically and using them. Have also researched in using protocols to solve my problem but for some reason, I'm having an error.
I have tried implementing segues and giving them an identifier and then presenting them but didn't work as well. About what I meant by trying is I have used different commands such as dismiss, present, show but it did not view the button present on the next view controller. Here is the function of view controller class - 'Login Page' that has to move to the next view controller after the if statement so that it can show the view controller - 'Login Page' with the button.
func handleLogin() {
//Guard statements are useful for form validation
guard let email = emailTextField.text, let password = passwordTextField.text
else{
print("Form not valid")
return
}
Auth.auth().signIn(withEmail: email, password: password) { (user, error) in
if error != nil{
let alert = UIAlertController(title: "Sorry!", message: "Incorrect Credentials", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
print(error)
return
}
//Need to do the code here that can display the next view controller with the button
}
}
Kindly help, please.

To segue programmatically to a Storyboard view, you'll need to instantiate the next view controller then call pushViewController if using a navigationController or you can use self.present(). Here is an example using a navigationController.
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "NextViewController") as! NextViewController
return controller
self.navigationController?.pushViewController(controller, animated: true)
Note that you should change the storyboard name "Main" to whatever your storyboard filename is. You should also change "NextViewController" to whatever your controller storyboard identifier is (see Storyboard ID in image below). Also make sure to set your custom class in the storyboard (see Custom Class in image below).

Related

Display an alert using storyboard and custom UITabBarController

I am dealing with a problem using UITabBarController. I have a small project using storyboards (XCode 13, IOS 15 as base system). I created a TabBarController but I later discovered I could not manage it effectively programmatically. Reading various docs, I discovered I could use two scenes from my storyboard and creating the tabbar programmatically.
So I did this in SceneDelegate.swift:
let queryViewControllerTab = storyBoard.instantiateViewController(withIdentifier: "QueryViewController")
let settingsViewControllerTab = storyBoard.instantiateViewController(withIdentifier: "SettingsViewController")
let starredViewControllerTab = storyBoard.instantiateViewController(withIdentifier: "StarredViewController")
starredViewControllerTab.tabBarItem.title = "Starred"
starredViewControllerTab.tabBarItem.image = UIImage(systemName: "star")
// TODO: Discover why first two views keep reading image I setup previously in storyboard
let tabBarController = UITabBarController()
tabBarController.viewControllers = [queryViewControllerTab, settingsViewControllerTab, starredViewControllerTab]
tabBarController.selectedViewController = settingsViewControllerTab
self.window?.rootViewController = tabBarController
self.window?.makeKeyAndVisible()
This works perfectly and I can easily put a condition whether userDefaults are not set, load directly the settings.
In my class SettingsViewController I want to add an action where, upon pressing the button, you get an alert:
#IBAction func saveButtonPressed(_ sender: UIButton) {
// keychain.set(tokenInput.text ?? "", forKey: keychainKey)
let alert = UIAlertController(title: "My Alert", message: "This is an alert.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Default action"),
style: .default, handler: { _ in
NSLog("The \"OK\" alert occured.")
}))
tabBarController.present(alert, animated: true, completion: nil)
}
But this makes the app crashing with unrecognized selector sent to instance 0x7f82f9705c30'
I've tried to debug the problem, and I understood I can't make the alert in this way because the view is really the tabBar and not the my scene. But here I got stuck.
I tried to implement the UITabBarControllerDelegate, in StarredViewController, but I can't get it working.
extension StarredViewController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
print("did select tab bar item!")
}
}
I start thinking my main setup with SceneDelegate and AppDelegate is wrong.
Most of previous tutorials or threads I've found seems to fail even to compile because using deprecated versions.
This is a way to present an alert from any presented View Controller.
Add some extensions:
import UIKit
extension UIViewController {
var customVisibleViewController: UIViewController? {
if let navigationController = self as? UINavigationController {
return navigationController.topViewController?.customVisibleViewController
} else if let tabBarController = self as? UITabBarController {
return tabBarController.selectedViewController?.customVisibleViewController
} else if let presentedViewController = presentedViewController {
return presentedViewController.customVisibleViewController
} else if self is UIAlertController {
return nil
} else {
return self
}
}
}
extension UIApplication {
/// The top most view controller
static var topMostViewController: UIViewController? {
return UIApplication.shared.keyWindow?.rootViewController?.customVisibleViewController
}
}
Now you can show your alert in this way:
let alert = UIAlertController(title: "My Alert", message: "This is an alert.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Default action"),
style: .default, handler: { _ in
NSLog("The \"OK\" alert occured.")
}))
UIApplication.topMostViewController?.present(alert, animated: true, completion: nil)
This is the code to trigger an alert. With addAction, you can add possible answers.
do {
try //some method call or something else
} catch {
let alert = UIAlertController(title: "There was an error while saving!", message: "Please try again", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "I understand", style: .cancel, handler: nil))
}
you can find more information here:
how to show an alert
To get to the root Controller you can use the following code:
let viewController = UIApplication.shared.windows.first!.rootViewController as! YourViewController
I solved the problem. Actually, all my assumptions and the question were wrong.
TL;DR the storyboard is corrupted or got damaged when I removed the tab bar from it to make it programmatically.
Here the long version. Before entering in this trouble, I had a storyboard with two views and a tab bar controller. It was working perfectly. At one point, I decided I wanted to make a choice during the app starting and, in case of missing defaults, load immediately the settings view. I found that, to do this, I had to move my tab bar down to the scene delegate and remove it from storyboards. I did it, so storyboard was showing to views no linked, and I instantiated the tab bar from the scene delegate.
Weirdly, the tab bar being rendered was still showing some properties previously set on the storyboard, even if that component was deleted.
Then, you know the problem. My reasoning did not make any sense. The UITabBarController can't show any alert. Alerts can be presented on a UIViewController only. So, it was pointless to keep trying to make an alert out from a tab bar.
This wrong understanding led me also to wrong research which reported various similar questions (probably misleading).
I finally made a counter test. Created a brand new project with storyboard. Created two views on the storyboard and defined a tab bar controller on the scene delegate. It worked as expected. I linked each view to a specific UIViewController. Created a button on the view, added the IBAction and it worked. Then, I created the alert in the IBAction and, this time, worked exactly.
I ended with the same code, and the only different is that I did not create and removed a tab bar from the storyboard.
I knew that storyboard can get damaged and, probably, I did it.

Display alert from custom cell in swift 3

I have designed custom cells in listViewCell.swift and listViewCell.xib. In which i have textfield. When the user enters a number more than 100 then i want to show alert.
I have tableView in HomeViewController.
let alert = UIAlertController(
title: "Error",
message: "Please Insert value less than 100",
preferredStyle: UIAlertControllerStyle.alert
)
alert.addAction(UIAlertAction(
title: "Click",
style: UIAlertActionStyle.default,
handler: nil
))
UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil)
then it gives me Attempt to present <UIAlertController: 0x7f945a5a67d0> on <ProjName.LoginController: 0x7f945a407e70> whose view is not in the window hierarchy!
and when i change the code to
let alert = UIAlertController(
title: "Error", message: "Please Insert value less than 100",
preferredStyle: UIAlertControllerStyle.alert
)
alert.addAction(UIAlertAction(
title: "Click",
style: UIAlertActionStyle.default,
handler: nil
))
HomeViewController().present(alert, animated: true, completion: nil)
then it gives me unexpectedly found nil while unwrapping an Optional value
and points to
override func viewDidLoad() {
super.viewDidLoad()
**tableViewList.delegate = self**
tableViewList.dataSource = self
searchBar.delegate = self
}
How can i show a alert from a custom cell? or how can we create a common alert function in HomeViewController and show it form any swift file?
Thanks in Advance
You could go with making delegates of your custom cell. Set the delegate of your custom cells, let's say didEnterInvalidValue didEnterValidValue, to your HomeViewController. From those delegate implementations, show your UIAlertController with custom messages.
Or you can iterate over your self.navigationController?.viewControllers to find the top view controller and show UIAlertController on that view controller's view.
Most easiest would be
Step 1:
From your View controller where you have used your custom Cell, make a common delegate function for your textField which purpose is to notify you about text length.
You may need to extend UITextFieldDelegate for this.
Or u can assign a target function to your textField(Not sure if it works though)
How do you catch Specific TextField?
You need to assign a tag for every textField using indexPath and catch that specific textField using that tag.
Step 2:
In that function you can show your alert by only using
self.present(alert, animated: true, completion: nil)
I would go for the delegate answer. But for a faster solution, at several projects I create a singleton from the rootViewController (generally the UINavigationController) at AppDelegate so it can get the rootController any time. Using keyWindow, it's not very recommended since the keyWindow it's not always the one controlled by who, for example, alert dialog redefine it.
Another projects I create a AlertClass singletone, where it's initialised by the rootController, and it's always invoked from it.

Unable to perform segue due to no segue with identifier error

How can I do so the user gets taken to a custom view controller when he/she taps on "okay" button on alert?
I have a main controller -> navigation controller -> second view controller.
Main View Controller has a segue "mainStoryBoard"
The alert gets generated on the second view controller.
This is what I have
let alertController = UIAlertController(title: "title", message: "message", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { action
in self.performSegue(withIdentifier: "mainStoryBoard", sender: self)
}))
present(alertController, animated: true, completion: nil)
return
The error I am getting is the following:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver () has no segue with identifier 'mainStoryBoard'
I have set the identifier to 'mainStoryBoard' and still no luck.
Any guidance will be greatly appreciated.
Thanks to #MikeG's guidance I was able to get it to work.
The problem was that I created a segue from the main view controller to the second view controller.
Solution
Create a segue from the second view controller to the main view controller and if you have a navigation controller, create the segue from the second view controller to the navigation controller.
The code remained the same.
How are you loading your main view controller?. Try to instantiate using instantiateviewcontrollerwithidentifier not with alloc init. Still if you are facing this problem then try to clean the project and run.

how to call view controller with navigation controller

I have one view controller called login VC with navigation view controller.And i connect two button in my LoginVc.First button will navigate to signUp Screen as push segue.Second button will navigate to forgot password screen as push segue.
Now when user logged in and enter in to my app means.There is one button name called Log out.I have written the code functionality like, when user press log out button one uialert pop up will ask "Do You want to log out".If user press they will navigate to again login screen .
So after user pressed log out and if user came to login screen and if user press my two button [ i. e means forgot password screen, sign up screen button].Then i am getting crash.
Crash report :
Terminating app due to uncaught exception 'NSGenericException', reason: 'Push segues can only be used when the source controller is managed by an instance of UINavigationController.'
My log out button code :
#IBAction func LogoutButton(sender: AnyObject) {
NSUserDefaults.standardUserDefaults().setBool(false, forKey: "ISLOGGEDIN")
NSUserDefaults.standardUserDefaults().synchronize()
dispatch_async(dispatch_get_main_queue(), {
let alert = UIAlertController(title: "Logout Successful", message: "You have successfully logged out.", preferredStyle: UIAlertControllerStyle.Alert)
// add an action (button)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default){action -> Void in
let loginViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LoginViewController") as? LoginViewController
let appDelegate:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = loginViewController
})
// show the alert
self.presentViewController(alert, animated: true, completion: nil)
})
}
I try adding this line :
let vc: UINavigationController = storyboard.instantiateViewControllerWithIdentifier("LoginViewController") as! UINavigationController
But i am getting same problem.Now how can i re write my code in my log out button action method.To redirect to login screen with navigation bar.
Please help me out.Thanks!
You are setting the RootViewController to a ViewController but your application is NavigationViewController. Your RootViewController should be a NavigationController. So add your LoginViewController to NavigationViewController and set that rootViewController of window.
let loginViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LoginViewController") as? LoginViewController
let navigationController = UINavigationController(rootViewController: vc)
window.rootViewController = navigationController
why don't you do like this:
self.navigationController?.pushViewController(loginViewController!, animated: true)
and when you want to come back:
self.navigationController?.popViewControllerAnimated(true)
According to your error there is no navigation controller found in your view hierarchy.
Select your loginViewController from story board and from Editor embed in Navigation controller. and use performSegue to push viewcontroller and use popViewController or popTorootviewcontroller to go back in navigation controller.
hope this will help :)
Click to viewController in storyBoard
Go to Editor -> Embed In -> Navigation Controller this will add navigation on this view controller.
Then you can push and pop from one view controller to another view controller.
Ex : Push
var dash : XyzViewController = storyBoard.instantiateViewControllerWithIdentifier("xyz") as! XyzViewController
self.navigationController?.pushViewController(dash, animated: true)
Pop:
self.navigationController?.popViewControllerAnimated(true)
Try this solution:
let vc = self.storyboard?.instantiateViewController(withIdentifier: "yourVC") as! yourViewController
let navigationController = UINavigationController(rootViewController: vc)
self.present(navigationController, animated: true, completion: nil)

Multiple Storyboards - Multiple Navigation Controllers

I am breaking up my project into multiple storyboards. I have a login storyboard with a navigation controller. The last step "Complete Registration" takes you to main.storyboard.
This leaves the navigation controller in place, and the back button takes you back to register. I obviously don't want that. I know I can hide the bar by using:
self.navigationController?.navigationBarHidden = true
But how can I leave the navigation controller?
Any help would be greatly appreciated!
The easiest and most correct way to do this would be to make your login screen a modal view. If you don't want to give your user the ability to go back, then you shouldn't use a push segue. You should use a modal view.
In the iOS Human Interface guidelines it says that modal views should be used for "a self-contained task [that] must be completed—or explicitly abandoned—to avoid leaving the user’s data in an ambiguous state."
A modal view will make it so your login and your main screen have to be in separate Navigation Controllers.
In the completion handler of the "Complete Registration" action you'll want to use the following code.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateInitialViewController() as! (TypeOfViewController)
self.presentViewController(viewController, animated: true, completion: nil)
If your main.storyboard is already embedded in a NavigationController (Initial view controller is a UINavigationController) then you'll have to do that a little bit differently.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateInitialViewController()
self.presentViewController(viewController, animated: true, completion: nil)

Resources