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.
Related
I got simple UIViewController + UIAlert extension:
extension UIViewController {
func alert(title: String = "", message: String){
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: Localized.ok(), style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}
}
Within the ViewController I got a method:
func findUser() {
userService.findUser { (userinfo, error) in
if error != nil {
if let errText = error?.localizedDescription {
self.alert(message: errText)
}
self.doAuth()
return
}
}
}
this doAuth() method should redirect to loginViewController using:
navigationController?.pushViewController(loginViewController, animated: false)
The problem is, that in this scenario, this push doesn't work (nothing appears) (I click OK button on the alert, alert dissapears but loginViewController is not pushed)
I refactored extension a little bit:
extension UIViewController {
func alert(title: String = "", message: String, action completion: (() -> Void)? = nil){
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: Localized.ok(), style: .default, handler: { _ in
completion?()
}))
present(alert, animated: true, completion: nil)
}
}
so findUser() method is calling doAuth() in differently:
func findUser() {
userService.findUser { (userinfo, error) in
if error != nil {
if let errText = error?.localizedDescription {
self.alert(message: errText){ [weak self] in
self?.doAuth()
}
}
return
}
}
}
and it works!
Problem is I have no idea why. And what could have happened in the first scenario?
I feel it should be some simple explanation, but I can't figure it out.
Edit
The explanation is simple and was printed in the console:
pushViewController:animated: called on <UINavigationController 0x7f86050b4400>
while an existing transition or presentation is occurring; the navigation stack
will not be updated.
So doAuth() (with pushing VC method) was called while alert was visible/presented, so alert took the focus and VC couldn't be pushed.
cc: #Paulw11 # cookednick
Problem is navigating and presenting in same if statement
If error != nil , means it only show the alert not try to doAuth().
But you are calling doAuth() in same if block then it is trying to present alert as well as to navigate
func findUser() {
userService.findUser { (userinfo, error) in
if error != nil {
if let errText = error?.localizedDescription {
self.alert(message: errText)
}
return
}
//Out side if block
self.doAuth()
}
}
I'm trying to detect when the users tap on the "Add" button in the PKAddPassesViewController.
I added addPassesViewControllerDidFinish() so that when passVC is dismissed, function addPassesViewControllerDidFinish() will be called.
override func viewDidLoad() {
self.pass = try PKPass(data: downloadedData! as Data)
let passVC = PKAddPassesViewController(pass: self.pass)
self.present(passVC!, animated: true)
// when passVC is dimissed by the user, addPassesViewControllerDidFinish is expected to be called, but it never gets called.
}
func addPassesViewControllerDidFinish(_ controller: PKAddPassesViewController) {
print("enter DidFinish")
let passLib = PKPassLibrary()
// Get your pass
guard let pass = self.pass else { return }
if passLib.containsPass(pass) {
print("if start")
// Show alert message for example
let alertController = UIAlertController(title: "", message: "Successfully added to Wallet", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
controller.dismiss(animated: true, completion: nil)
}))
controller.show(alertController, sender: nil)
print("if end")
} else {
// Cancel button pressed
print("else start");
controller.dismiss(animated: true, completion: nil)
print("else end");
}
}
However, when passVC is dimissed by the user, func addPassesViewControllerDidFinish() never gets called at all.
There are three things you should fix:
1. Extend PKAddPassesViewControllerDelegate in your ViewController class.
2. Double check if you added delegate to your PKAddPassesViewController: VC?.delegate = self, which will link your delegate to PKAddPassesViewControllerDelegate.
3. Inside addPassesViewControllerDidFinish, dismiss controller first. Then do whatever you want inside passLib.containPass. The alertController is no longer belonged to the controller, maybe to its parent view.
I have an app where a user uploads a file in the background that usually takes a couple of seconds. The upload is kicked off when they tap a "Done" button and that also dismisses the view controller. What I would I would like to happen is an alert comes up when the download is done. I thought I would just add the code below to the upload function but it isn't working. How can I have an alert box appear to confirm that that the upload was successful?
#IBAction func tapDone(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
if let image = newImage {
submit2Parse(image: image)
}
}
func submit2Parse (image: UIImage) {
if let pfImage = image2PFFile(image: image) {
// Insert PFFile into parse server
let submittedImage = PFObject(className: "Images")
submittedImage["imageFile"] = pfImage
submittedImage["type"] = "submittedFromUserHome"
submittedImage["bride"] = brideSwitch.isOn
submittedImage["groom"] = groomSwitch.isOn
submittedImage["user"] = userSwitch.isOn
submittedImage["picturePeriod"] = pickerSelected
submittedImage["uploadedByUserId"] = PFUser.current()?.objectId ?? "" submittedImage["uploadedByUserName"] = PFUser.current()?.username ?? ""
if !txtIsPlaceHolder { submittedImage["description"] = imageDescription.text }
submittedImage.saveInBackground { (success, error) in
if success {
let message = "Save in bg worked"
print(message)
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: {
(action) in
self.dismiss(animated: true, completion: nil)
}))
self.present(alert,animated: true, completion: nil)
} else {
print(error?.localizedDescription ?? "")
}
}
}
}
The code gives me this build error:
Implicit use of 'self' in closure; use 'self.' to make capture semantics explicit
In your tapDone method, you need to utilize the completion of the dismissal of your controller, like so:
self.dismiss(animated: true) {
if let image = newImage {
self.submit2Parse(image: image)
}
}
This only means that the self.submit2Parse(image: image) will ONLY be executed after you dismiss the controller. Additionally, the error
Implicit use of 'self' in closure; use 'self.' to make capture
semantics explicit
only means that you need to use self. to explicitly call a variable or method when you're inside a closure. So your submit2Parse... would now be like this:
self.submit2Parse(image: image).
If I understood your question correctly you want to dismiss your SecondViewController when user tap on tapDone button. And after that once your image upload complete then you want to present alert for success.
But if you dismiss it once button tapped you won't get any alert because your SecondViewController is no longer in window hierarchy and if you will try to present the alert you will get an error in Console like:
Attempt to present on
whose view is not in the
window hierarchy!
To solve this problem you need to present a alert from your first view controller which will appear after you dismiss your second view controller and you can achieve it with delegate.
Consider the below example:
First of all declare a protocol outside your FirstViewController:
protocol UplaodSuccessDelegate:class {
func uploadSuccess(message: String)
}
then confirm your delegate to same class:
class ViewController: FirstViewController, UplaodSuccessDelegate {
then you need to pass the delegate when you present SecondViewController:
let vc = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
vc.delegate = self. //Pass delegate
self.present(vc, animated: true, completion: nil)
and add delegate method to same class:
func uploadSuccess(message: String) {
let message = "Save in bg worked"
print(message)
let alert = UIAlertController(title: "title", message: message, preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: {
(action) in
}))
self.present(alert,animated: true, completion: nil)
}
now in your SecondViewController you need to add
weak var delegate: UplaodSuccessDelegate?
and in your tapDone method replace a code with:
self.dismiss(animated: true) {
if let image = newImage {
submit2Parse(image: image)
}
}
and once your upload complete you need to call delegate method (once your upload completes) like:
self.delegate?.uploadSuccess(message: "your message")
This will call your delegate method from FirstViewController.
When you drop the function below into the viewController that initiates the data upload in the background, and then call this function in the closure that fires when the submit completes it is able to successfully create an alert even thought the initial view controller was dismissed (or long dismissed if it was a long upload).
// This is the function that performs my background upload
func submit2Parse (image: UIImage) {
if let pfImage = image2PFFile(image: image) {
// Insert PFFile into parse server
let submittedImage = PFObject(className: "Images")
submittedImage["imageFile"] = pfImage
submittedImage["imageCreateDt"] = newImageCreateDate
submittedImage["type"] = "submittedFromUserHome"
submittedImage["bride"] = brideSwitch.isOn
submittedImage["groom"] = groomSwitch.isOn
submittedImage["user"] = userSwitch.isOn
submittedImage["picturePeriod"] = pickerSelected
submittedImage["uploadedByUserId"] = PFUser.current()?.objectId ?? ""
submittedImage["uploadedByUserName"] = PFUser.current()?.username ?? ""
if !txtIsPlaceHolder { submittedImage["description"] = imageDescription.text }
// Get the image timestamp, every photo has one
// How do you get the thumbnail image too?
submittedImage.saveInBackground { (success, error) in
if success {
let message = "Save in bg worked"
print(message)
self.showAlertFromAppDelegates()
} else {
print(error?.localizedDescription ?? "")
}
}
}
}
// This is the function that creates the alert
func showAlertFromAppDelegates(){
var topWindow: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
topWindow?.rootViewController = UIViewController()
topWindow?.windowLevel = UIWindow.Level.alert + 1
let alert: UIAlertController = UIAlertController(title: "Upload Complete", message: "Your image was successfully submitted!", preferredStyle: .alert)
alert.addAction(UIAlertAction.init(title: "OK", style: .default, handler: { (alertAction) in
topWindow?.isHidden = true
topWindow = nil
}))
topWindow?.makeKeyAndVisible()
topWindow?.rootViewController?.present(alert, animated: true, completion:nil)
}
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
}
}
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