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)
}
Related
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.
Alright, I've looked up a lot of documentation on how the UIAlertController's work and I have a rather unique scenario. My application is being designed to work with a HID Bluetooth scanner. When I use:
preferredStyle: UIAlertControllerStyle.Alert
After I generate an alert that the item I have scanned is incorrect. Cool, alert is happening, problem is if I scan again ( which emulates keyboard input ), the return key is being sent to the alert and the alert is running the dismiss action.
If I use:
preferredStyle: UIAlertControllerStyle.ActionSheet
Then the alert is staying where it should be and ignoring scans while the alert window is up just how I want it.
So my question is, how do I capture the return key and prevent the Alert from calling the dismiss action? I'm a bit new with swift and I have a feeling the answer is simple, but I've tried half a dozen things that just isn't working.
If there is a setting to prevent all user input to the alert window or anything solution, I'm all for any method. I just rather not use the ActionSheet, and prefer to use the iOS alerts instead of creating my own screen. If this is not possible, I'm sure I can build my own 'alerts' window.
Code, that I'm calling from a simple Alerts class I made.
import UIKit
class Alerts {
var controller: UIViewController
var message: String
var title: String
init?(title: String, message: String, controller: UIViewController){
if title.isEmpty || message.isEmpty {
return nil
}
self.title = title
self.message = message
self.controller = controller
}
func save_alert(input_field:UITextField, callable: (Bool)->Void ){
let action = UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default) {
UIAlertAction in
callable(false)
input_field.enabled = true
input_field.becomeFirstResponder()
print("DISMISS CALLED")
}
let alert = UIAlertController(title: self.title,message:self.message,preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(action)
self.controller.presentViewController(alert,animated:true, completion: nil)
}
}
Try something like this.
func save_alert(input_field:UITextField, callable: (Bool)->Void ){
let action = UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default) {
UIAlertAction in
callable(false)
input_field.enabled = true
input_field.becomeFirstResponder()
print("DISMISS CALLED")
showAlert()
}
showAlert()
}
func showAlert() {
let alert = UIAlertController(title: self.title,message:self.message,preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(action)
self.controller.presentViewController(alert,animated:true, completion: nil)
}
I have built a game using SpriteKit and I have it fully functional except for getting the user to input their name to store with their high scores. I'm using NSUserDefaults to store the scores as an array of their top 5 scores. I'd like to store their name as well because eventually I plan to move the storage to a server instead of NSUserDefaults to allow for players to compete for high scores.
My thought was to present a UIAlertController when the game first runs on the device, to grab their name via a text input field, then store it in NSUserDefaults. But no matter where I put the code for UIAlertController (GameScene.swift, GameViewController.swift, and even AppDelegate.swift) it doesn't pop up.
The code I'm using for the alert is:
let ac = UIAlertController(title: "Enter Name", message: nil, preferredStyle: .Alert)
ac.addTextFieldWithConfigurationHandler(nil)
ac.addAction(UIAlertAction(title: "OK", style: .Default) { [unowned self, ac] _ in
let playerName = ac.textFields![0]
})
ac.presentViewController(ac, animated: true, completion: nil)
This is the UPDATED CODE based on comments below, including the entire viewDidLoad function:
override func viewDidLoad() {
super.viewDidLoad()
let ac = UIAlertController(title: "Enter Name", message: nil, preferredStyle: .Alert)
ac.addTextFieldWithConfigurationHandler(nil)
ac.addAction(UIAlertAction(title: "OK", style: .Default) { [unowned self, ac] _ in
let playerName = ac.textFields![0]
})
self.presentViewController(ac, animated: true, completion: nil)
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
currentGame = scene
scene.viewController = self
}
}
Presenting a view controller won't generally work inside of viewDidLoadsince it is called after the view is loaded into memory, but before it is presented on the screen (see the view controller lifecycle here). You can set things up there (like your scenes) but any animations or interactive elements must be done later.
A safer place to put this would be in viewDidAppear (along with some logic to make sure it doesn't get repeatedly presented) or in response to a tap on a button.
Also, you have to call presentViewController from a view controller that is already on the screen for it to be shown. So, if you have this code somewhere in your GameViewController, you could change
ac.presentViewController(ac, animated: true, completion: nil)
to
self.presentViewController(ac, animated: true, completion: nil)
//1. Create the alert controller.
var alert = UIAlertController(title: "Some Title", message: "Enter a text", preferredStyle: .Alert)
//2. Add the text field. You can configure it however you need.
alert.addTextFieldWithConfigurationHandler({ (textField) -> Void in
textField.text = "Some default text."
})
//3. Grab the value from the text field, and print it when the user clicks OK.
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: { (action) -> Void in
let textField = alert.textFields![0] as UITextField
println("Text field: \(textField.text)")
}))
// 4. Present the alert.
self.presentViewController(alert, animated: true, completion: nil)
is that's what you except?
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 :)
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