presentViewController not working in Swift - ios

Thank you for reading this. I would like to have a functions Swift file where I put all of the functions for my project into, that the other Swift files could call. I am trying to create an alert function in the functions file that, when I pass in a specific string, it shows a specific alert. It was working when it was in the main file, but when I moved it to the functions file, presentViewController is giving me an error, saying "Use of unresolved identifier 'presentViewController'." Please help! Here is my code:
in the functions file:
import Foundation
import UIKit
/**********************************************
Variables
***********************************************/
var canTapButton: Bool = false
var tappedAmount = 0
/**********************************************
Functions
***********************************************/
//the alert to ask the user to assess their speed
func showAlert(alert: String) -> Void
{
if(alert == "pleaseAssessAlert")
{
let pleaseAssessAlert = UIAlertController(title: "Welcome!", message: "If this is your firs time, I encourage you to use the Speed Assessment Tool (located in the menu) to figure which of you fingers is fastest!", preferredStyle: .Alert)
//ok button
let okButtonOnAlertAction = UIAlertAction(title: "Done", style: .Default)
{ (action) -> Void in
//what happens when "ok" is pressed
}
pleaseAssessAlert.addAction(okButtonOnAlertAction)
presentViewController(pleaseAssessAlert, animated: true, completion: nil)
}
else
{
println("Error calling the alert function.")
}
}
Thanks!

The presentViewController is the instance method of UIViewController class. So you can't access it on your function file like this.
You should change the function like:
func showAlert(alert : String, viewController : UIViewController) -> Void
{
if(alert == "pleaseAssessAlert")
{
let pleaseAssessAlert = UIAlertController(title: "Welcome!", message: "If this is your firs time, I encourage you to use the Speed Assessment Tool (located in the menu) to figure which of you fingers is fastest!", preferredStyle: .Alert)
//ok button
let okButtonOnAlertAction = UIAlertAction(title: "Done", style: .Default)
{ (action) -> Void in
//what happens when "ok" is pressed
}
pleaseAssessAlert.addAction(okButtonOnAlertAction)
viewController.presentViewController(pleaseAssessAlert, animated: true, completion: nil)
}
else
{
println("Error calling the alert function.")
}
}
Here, you are passing a UIViewController instance to this function and calling the presentViewController of that View Controller class.

In Swift 3:
The method presentViewController is replaced by present.
You can use it like the old one:
self.present(viewControllerToPresent, animated: true, completion: nil)

First, you need to check your NavigationController is appropriate or not?
If Yes, then Here is code for present and dismiss presentviewcontroller
For presenting PresentViewController :
let next = self.storyboard?.instantiateViewControllerWithIdentifier("Your view controller identifier") as! Yourviewcontroller
self.presentViewController(next, animated: true, completion: nil)
Dismiss Presentviewcontroller
self.dismissViewControllerAnimated(true, completion: nil)

I would say go with MidHun MP method above but, if you are looking for another way to do this without bringing in the UIViewController then:
func showAlert(alert : String) {
var window: UIWindow?
if(alert == "pleaseAssessAlert")
{
let pleaseAssessAlert = UIAlertController(title: "Welcome!", message: "If this is your firs time, I encourage you to use the Speed Assessment Tool (located in the menu) to figure which of you fingers is fastest!", preferredStyle: .Alert)
//ok button
let okButtonOnAlertAction = UIAlertAction(title: "Done", style: .Default)
{ (action) -> Void in
//what happens when "ok" is pressed
}
pleaseAssessAlert.addAction(okButtonOnAlertAction)
self.window?.rootViewController?.presentViewController(pleaseAssessAlert, animated: true, completion: nil)
}
else
{
println("Error calling the alert function.")
}
}

Presenting & navigation view controller has a problem with layoutsubviews function while using self.view or viewcontroller.view, so one must avoid those function.
Check:
func layoutsubviews not allows to provide the viewcontroller.view to work on it

Related

Prevent presenting the UIAlertViewController after navigating to the other view

I have one scenario when the user did not use the application for more than 5 min app will show a popup with session expiration message.
The code for session expiration is added in the appDelegate and from there the popup will be presented on the current view controller.
code is
#objc func applicationDidTimeout(notification: NSNotification) {
if (window?.rootViewController?.isKind(of: UITabBarController.self))! {
for view in window?.rootViewController?.view.subviews ?? [(window?.rootViewController?.view)!] {
if view.isKind(of: MBProgressHUD.self) {
return
}
}
if window?.rootViewController?.presentedViewController != nil {
window?.rootViewController?.dismiss(animated: true, completion: {
self.showMessage(message: Message.sessionTimeout)
})
} else {
self.showMessage(message: Message.sessionTimeout)
}
}
}
fileprivate func showMessage(message: String) {
let alert = UIAlertController(title: appName, message: message, preferredStyle: .alert)
let actionOkay = UIAlertAction(title: "OK", style: .default) { (action) in
DispatchQueue.main.async {
UIView.transition(with: self.window!, duration: 0.3, options: UIView.AnimationOptions.transitionCrossDissolve, animations: {
CommonFunctions.setLoginAsRootVC()
}, completion: nil)
}
}
alert.addAction(actionOkay)
self.window?.rootViewController?.present(alert, animated: true, completion: nil)
}
Now if the user is doing some data entry and at that time, if the user leaves application ideal for 5 min or more the keyboard will dismiss and the session expiration message shown there.
But as the text field's delegate method textFieldShouldEndEditing has some validation and if that validation fails it shows a popup with the message and ok button.
So when the user taps on the ok button in the session expiration message popup, it will redirect the user to the login screen but due to the text field's delegate method validation, it shows one pop up in the login screen.
Code for the validation fail message popup is
fileprivate func showErrorMessage(message: String) {
let alert = UIAlertController(title: appName, message: message, preferredStyle: .alert)
let actionOkay = UIAlertAction(title: "OK", style: .default) { (action) in
self.txtField.becomeFirstResponder()
}
alert.addAction(actionOkay)
self.present(alert, animated: true, completion: nil)
}
How to prevent the popup from being present in the login screen?
I try to get the proper way to prevent the popup from appearing on the login screen.
But Finally, I found one heck to solve this issue.
I have declared one boolean in AppDelegate and set it's value to false when I want to prevent the popup from appearing and then revert it back to true when I want to show the popup.
I know this is not the elegant or efficient solution for the issue, but it works for now.
If anyone knows the better answer can post here, I'm still open to any better solution.
#objc func applicationDidTimeout(notification: NSNotification)
{
let visibleView : UIViewController = self.getVisibleViewControllerFrom(self.window?.rootViewController)!
self.showMessage(message: Message.sessionTimeout,Controller: visibleView)
}
fileprivate func showMessage(message: String , Controller : UIViewController) {
let alert = UIAlertController(title: appName, message: message, preferredStyle: .alert)
let actionOkay = UIAlertAction(title: "OK", style: .default) { (action) in
//Now apply your code here to set login view controller as rootview
// This controller is for demo
window!.rootViewController = UIStoryboard(name: "Main", bundle:
nil).instantiateViewController(withIdentifier: "loginview")
window!.makeKeyAndVisible()
}
alert.addAction(actionOkay)
Controller.present(alert, animated: true, completion: nil)
}
//MARK:- Supporting method to get visible viewcontroller from window
func getVisibleViewControllerFrom(_ vc: UIViewController?) -> UIViewController? {
if let nc = vc as? UINavigationController {
return self.getVisibleViewControllerFrom(nc.visibleViewController)
} else if let tc = vc as? UITabBarController {
return self.getVisibleViewControllerFrom(tc.selectedViewController)
} else {
if let pvc = vc?.presentedViewController {
return self.getVisibleViewControllerFrom(pvc)
} else {
return vc
}
}
}
Try this code, I've use this code many times may be it's work for you.

How do I display an alert popup from another Class that is not a UIViewController?

I have the following Swift 3 code in a separate swift file in another class.
class Login{
func showAlert(message :String){
let alertController2 = UIAlertController(title: "Error", message: "A error occured when checking credentials, try again later.", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController2.addAction(defaultAction)
self.present(alertController2, animated: true, completion: nil)
}
}
But I get a red error:
Use of unresolved identifier 'UIAlertController'
How can I create a popup informing the user that something went wrong?
First you need to import UIKit in order to make UIAlertController visible to your class.
This code will obtain the current view controller even if it's a modal.
func topViewController() -> UIViewController? {
guard var topViewController = UIApplication.shared.keyWindow?.rootViewController else { return nil }
while topViewController.presentedViewController != nil {
topViewController = topViewController.presentedViewController!
}
return topViewController
}
So you can now obtain the controller and present the alert on it:
topViewController()?.present(alertController2, animated: true, completion: nil)
Firstly, you need:
import UIKit
But your larger problem is that the present() method you're trying to use is a method of UIViewController objects, and also it only works on View Controllers whose view is already a part of the view hierarchy.
So I think you need to refactor your code a bit, and decide which view controller should launch your alert.
import Foundation
import UIKit
class Utility: NSObject{
func showAlert(_ VC : UIViewController, andMessage message: String , handler :#escaping(UIAlertAction) -> Void){
let alert = UIAlertController(title: "Error", message: message , preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style:UIAlertActionStyle.default, handler: handler))
VC.present(alert, animated: true, completion: nil)
}
}
Try this, this will work.

Don't show Alert when the ViewController is not in Window hierarchy

I am having a NavigationController. In the ThirdViewController I am performing some task and on failure, I show Alert messages using UIAlertController.
Sometimes, when I start the task and come back to SecondViewController, I get the error message displayed on SecondViewController and on clicking OK, everything gets black below Navigation bar. I am left with only Navigation bar and if I go back again to FirstViewController, it also has the same black view except Navigation bar.
Presenting Alert of the ViewController which is not in the window hierarchy creates the issue. I do not want the Alert to be presented if I am not on the screen.
It is easily reproducible if I go back swiping the ViewController slowly.
What is the best way to handle it?
Sharing my code,
Button action in ThirdViewController
func buttonTapped() {
APIManager.sharedManager.getDetails(completion: { (details ,error) -> Void in
guard error == nil else {
Alert.errorMsg(error!.localizedDescription, viewController: self, goBack: false)
return
}
print(details)
}
}
class Alert: NSObject {
/* Error message */
class func errorMsg(message: String, viewController: UIViewController?, goBack: Bool = false) {
let alertView = UIAlertController(title: "Error", message: message, preferredStyle: .Alert)
let action = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { (alert: UIAlertAction) -> Void in
if goBack == true && viewController != nil {
viewController!.navigationController?.popToRootViewControllerAnimated(true)
}
}
alertView.addAction(action)
let controller = viewController ?? UIApplication.sharedApplication().keyWindow?.rootViewController
controller!.presentViewController(alertView, animated: true, completion: nil)
}
}
I created a CustomViewController and added a property 'isUnloading'. In viewWillDisappear, I set isUnloading = true. I check the property before presenting the Alert.
Since you did not share any code we don't know exactly what happens there. But if you do not want to show the alert if the view controller is not in the window hierarchy you could check if viewController.view.window is set before showing the alert view and show it only if it is set.
you can do something like,
class AlertHelper {
func showAlert(fromController controller: UIViewController) {
var alert = UIAlertController(title: "abc", message: "def", preferredStyle: .Alert)
controller.presentViewController(alert, animated: true, completion: nil)
}
}
called alert as,
var alert = AlertHelper()
alert.showAlert(fromController: self)
refer this link for more detail.
Hope this will help :)

Check if alertview is displayed

In my app I display tow alert views. The second alert view should pop up if the first has been closed. Now I check if an alert view is displayed like this:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let viewController = appDelegate.window!.rootViewController as! ViewController
if viewController.view.window != nil {
}
else {
let alertView = UIAlertController(title: NSLocalizedString("IAP", comment: "comment"), message: NSLocalizedString("IAP1", comment: "comment"), preferredStyle: .Alert)
alertView.addAction(UIAlertAction(title: "Ok", style: .Cancel, handler: nil))
viewController.presentViewController(alertView, animated: true, completion: nil)
}
I send the 2nd alert view if the first isn't displayed anymore. But if the first view is still displayed the 2nd alert view doesn't pops up anymore. So my question is if there's a waiting line for alert views and how can I solve this problem?
You should define a handler for the first action and present the 2nd alertView within the handler.
So instead of
UIAlertAction(title: "Ok", style: .Cancel, handler: nil)
you should do
UIAlertAction(title: "Ok", style: .Cancel) { (action) -> Void in
// Present the other alertView
}
If you are using a navigation controller, the general way to see if an alert is already displayed is to check the presentedViewController property.
if let _ = navigationController.presentedViewController {
print("is already presenting \(navigationController.presentedViewController)")
} else {
navigationController.presentViewController(alert, animated:true, completion:nil)
}
For a fast patch in any UIViewController : (Xcode 8.3.1 & Swift 3.1)
func blabla() {
if presentedViewController != nil {
delay(0.5, closure: {
self.blabla()
})
return
}
// other alert code here
}
Simple way to check in Swift
var isAlertViewPresenting: Bool {
get {
if self.presentedViewController is UIAlertController {
return true
}
return false
}
}

alertControllers in swift

I am having a couple of issues with alert controllers in swift. I have two functions for displaying activity indicators. 1 with animation, and one without. The reason for creating the second one without an animation was because.. I am displaying an activity on a view controller when a user clicks on a table view cell and is segued to a new controller. This controller calls a webservice and populates a second table view.
My problem was that the web service was returning a response so quick that the activity indicator wasn't up on screen when I was trying to dismiss it i.e. in the repsonse of the webservice call. I was presenting this indicator in the viewdidload and then calling the web service function in the view did load after.
The only way i could get around this was to create an activity alert which did not have an animation as it seemed as though the animation was slowing it down a bit. But when I set the animation property to false, the alert controller did not have a backgroundColor. When I try to add a background color to the alert controller, the width changes to full screen.
So I'm looking for:
A) a way around dismissing the regular alert controller when the web service returns too quickly
or
B) to reduce the size of the second alert controller which has no animation.
Thanks in advance. I was having a lot of trouble with dismissing these alert controllers in that when I was attempting to dismiss them, my actual view controller was being dismissed so I tried to check the class of the presentedController and only dismissing if the class was alertController but I don't think this is actually the right way to go around it at all.
Code below:
func displayActivityAlert(title: String, #ViewController: UIViewController)
{
let pending = UIAlertController(title: "\n\n\n"+title, message: nil, preferredStyle: .Alert)
//create an activity indicator
let indicator = UIActivityIndicatorView(frame: pending.view.bounds)
indicator.autoresizingMask = .FlexibleWidth | .FlexibleHeight
indicator.color = UIColor(rgba: Palette.accent)
//add the activity indicator as a subview of the alert controller's view
pending.view.addSubview(indicator)
//pending.view.backgroundColor = UIColor.whiteColor()
indicator.userInteractionEnabled = false // required otherwise if there buttons in the UIAlertController you will not be able to press them
indicator.startAnimating()
ViewController.presentViewController(pending, animated: true, completion: nil)
}
and
func displayActivityAlertNoAnim(title: String, #ViewController: UIViewController)
{
let pending = UIAlertController(title: "\n\n\n"+title, message: nil, preferredStyle: .Alert)
//create an activity indicator
let indicator = UIActivityIndicatorView(frame: pending.view.bounds)
indicator.autoresizingMask = .FlexibleWidth | .FlexibleHeight
indicator.color = UIColor(rgba: Palette.accent)
//add the activity indicator as a subview of the alert controller's view
pending.view.addSubview(indicator)
pending.view.backgroundColor = UIColor.whiteColor()
// this line cause the alert controller to become full width of the screen
indicator.userInteractionEnabled = false // required otherwise if there buttons in the UIAlertController you will not be able to press them
indicator.startAnimating()
ViewController.presentViewController(pending, animated: **false**, completion: nil)
}
Code for checking class and dismissing:
if self.presentedViewController!.isKindOfClass(UIAlertController){
self.dismissViewControllerAnimated(true, completion: nil)
}
You need to make use of the completion parameter in presentViewController(). This is a closure which will get executed exactly after the UIAlertController has become visible on the screen.
Now, I can only provide you with some pseudocode since you haven't provided any code on how you download or the callback you receive after downloading, but try something like the following:
func displayActivityAlert(title: String, #ViewController: UIViewController) {
let pending = UIAlertController(title: "\n\n\n"+title, message: nil, preferredStyle: .Alert)
...
ViewController.presentViewController(pending, animated: true) { () -> Void in
// Start downloading from webservice
}
}
And dismissing:
if self.presentedViewController!.isKindOfClass(UIAlertController){
self.dismissViewControllerAnimated(true) { () -> Void in
// Perform segue to tableview
}
}
UPDATE 1:
Updated pseudocode based on OP's architecture.
If you have factorised the code for your alerts into a separate file, then simply pass in the completion handler as a parameter like so:
func displayActivityAlert(title: String, #ViewController: UIViewController, completionHandler: ()->() ) {
let pending = UIAlertController(title: "\n\n\n"+title, message: nil, preferredStyle: .Alert)
...
self.presentViewController(pending, animated: true, completion: completionHandler)
}
And then whenever you call displayActivityAlert, then simply specify the callback, for example like so:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
displayActivityAlert("Hello", ViewController: self) { () -> () in
// Download from webservice
}
}
simple code modify as you need
put this code inside a function or inside a button of action
will have a single button "OK"
let alertView = UIAlertController(title: "Your ERROR Heading!", message: "Your error message here", preferredStyle: .Alert)
let OKAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
alertView.addAction(OKAction)
self.presentViewController(alertView, animated: true, completion: nil)
class func alertController(_ title:String, message: String, okTitle: String,cancelTitle: String? = nil,cancelCompletion:(() ->Void)? = nil, okCompletion :(() -> Void)?) {
let alertController = UIAlertController.init(title: title as String, message: message as String, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction.init(title: okTitle as String, style: UIAlertActionStyle.default) { (alertAction :UIAlertAction) in
if okCompletion != nil{
okCompletion!()
}
}
alertController.addAction(okAction)
if cancelTitle != nil && !(cancelTitle?.isEmpty)!{
let cancelAction = UIAlertAction.init(title: cancelTitle, style: UIAlertActionStyle.cancel) { (alertAction : UIAlertAction) in
if cancelCompletion != nil{
cancelCompletion!()
}
}
alertController.addAction(cancelAction)
}
Constant.Common.APPDELObj.navVC?.visibleViewController?.present(alertController, animated: true, completion: nil)
}

Resources