Standalone Alert in Swift? - ios

Is there a way to display an alert, when I may not have a handle on a ViewController?
I'm creating a centralized function for logging errors to the console, to my database, and ideally I'd like to pop up a notification for the user. I'd rather not have to pass in a ViewController every time I log an error.

You ca present it from your rootViewController or the current displayed viewController.
Since you start your application at least you have a UIWindow object and most
probably at least the rootViewController for that window.
UIApplication.shared.keyWindow!.rootViewController
You can present it from here anywhere in your code, just make a method an extension in UIApplication or UIViewController.
Or you can search for the top most view controller. Again you can make an extension in UIApplication that present your alert from there:
class func topViewController(rootViewController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
guard let rootViewController = rootViewController else {
return nil
}
guard let presented = rootViewController.presentedViewController else {
return rootViewController
}
switch presented {
case let navigationController as UINavigationController:
return topViewController(rootViewController: navigationController.viewControllers.last)
case let tabBarController as UITabBarController:
return topViewController(rootViewController: tabBarController.selectedViewController)
default:
return topViewController(rootViewController: presented)
}
}
Taking a an alert view as an example:
extension UIApplication {
static func presentAlert(with title: String, message: String) {
guard let rootVc = UIApplication.shared.keyWindow?.rootViewController else {
return
}
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Okay", style: UIAlertActionStyle.default, handler: nil))
rootVc.present(alert, animated: true, completion: nil)
}
}
}

You can always add a view on the window. Search about adding view to the window of the application.

Create a UIView and add it as the frontmost subview of the window (which is always readily available).
This could be a UIView subclass that loads from a nib, that has a label and a dismiss button, and so on. Thus it can simple and self-contained.
I actually use something like this for debugging purposes in my own apps. When I print to the console I also put up a temporary UILabel in front of everything so that I see what I'm logging.

Related

Swift 4 Attempt to present ViewController whose view is not in the window hierarchy

I am using swift 4 and I am trying to create an alertView when I there is an error while signing up a user using Firebase. I have an IBAction for the sign up button which will sign the user up using text from two textfields, one for email and one for password.
I am basically trying to show an alertview when there is an error with the sign up process, for example there is an empty textfield.
I have attached a screenshot of the function to where that is occuring. I know that I am in fact getting an error because the print statement outputs an error if there is one.
Regardless of if there is an error or not, there is no alert view showing up and the app performs the segue regardless.
2019-01-15 21:40:26.368924-0500 Pronto[9036:225268] Warning: Attempt
to present on
whose view is not in the
window hierarchy
This is the output that I am getting for the alertview now showing up.
I have looked at all the other posts about this same issue but none seem to work.
This issue happens due to your view hierarchy.
You need to find out what is your Current/Topmost view controller in
view hierarchy and present your alert over it.
To find out topmost view controller use following code:
func getTopMostViewController() -> UIViewController? {
var topMostViewController = UIApplication.shared.keyWindow?.rootViewController
while let presentedViewController = topMostViewController?.presentedViewController {
topMostViewController = presentedViewController
}
return topMostViewController
}
And present your alert over topmost view controller and use main thread to present an alert because closures may have working on another thread.
DispatchQueue.main.async {
getTopMostViewController()?.present(alertController, animated: true, completion: nil)
}
Please refer to this stack answer:
Swift 3 Attempt to present whose view is not in the window hierarchy
Try using ViewDidAppear instead of View did Load.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let alertViewController = UIAlertController(title: "Any", message: "Any Custom Message", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "ANy", style: .cancel, handler: nil))
present(alertViewController, animated: true, completion: nil)
}
You can get the top most view controller and have that view controller present the alert. So instead of self.present use this approach and see if it works:
let topViewController = UIApplication.shared.keyWindow?.rootViewController
topViewController?.present(alertController, animated: true, completion: nil)
Also try presenting on the main thread, since you're trying to show the alert in createUser completion handler:
DispatchQueue.main.async {
self.present(alertController, animated: true, completion: nil)
}
Check if you have "double tap" issue:
You double tap button accidentally
signUpBtnPressed is called twice
First request is executed correctly, thus launching the segue, and the error is nil
Second request returns error like 'user already exists', then trying to show alert from current controller, but segue is already launched and next controller is already presented
This is fixed by using loader with UI blocking (for example SVProgressHUD) - start loader at the beginning of the method and dismiss it in callback.

Warning: Attempt to present ZMNavigationController on **.ViewController whose view is not in the window hierarchy

Guys i am facing an odd problem with NavigationController. Existing answers did not help at all !!!!
Here is basic scenario of the app:
There are two views - Main and Second view
In main view there is a button when i happen to tap goes into second view using segue.
In second view after i enter a certain field in text view and click on a button called "join" it triggers "joinMeeting()" function
and meeting should be joined.
However, when i do that debugger shows me:
"Warning: Attempt to present on
<***.ViewController: *****> whose view is not in the window
hierarchy!"
So i have read most of the tread and given that it happens because of viewDidAppear method but i have nth to be done before viewDidAppear. Everything happens after button is clicked.
joinMeeting() is successfully called and print method returns 0 which means no issue(https://developer.zoom.us/docs/ios/error-codes/) and successful SDK connection however after this "Warning" error is shown in debugger and nothing happens in the app.
If it helps following is the code that triggers joinBtn:
/**
Triggers when Join Button is clicked from second view.
*/
#IBAction func joinMeeting(_ sender: Any) {
if( activityID.text == "" ) {
let alert = UIAlertController(title: "Field is Blank", message: "Activity ID cannot be blank.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
return;
}
let ms: MobileRTCMeetingService? = MobileRTC.shared().getMeetingService()
if ms != nil {
ms?.delegate = self;
// //For Join a meeting
let paramDict: [AnyHashable: Any] = [
kMeetingParam_Username: kSDKUserName,
kMeetingParam_MeetingNumber: activityID.text!,
]
let ret: MobileRTCMeetError? = ms?.joinMeeting(with: paramDict)
print("onJoinaMeeting ret:\(String(describing: ret))")
}
}
Please help if anyone knows or have an idea on what i am missing here.
Here is what solved the issue:
Storyboard Configuration:
ViewController --Segue: Show--> JoinViewController
#IBAction func onClickJoin(_ sender: AnyObject) {
//Main storyBoard
let initialVC = UIStoryboard(name: "Main", bundle:nil).instantiateInitialViewController() as! UIViewController
let appDelegate = (UIApplication.shared.delegate as! AppDelegate)
appDelegate.window?.rootViewController = initialVC
//Rest of the code
}
Just Add Following code on that controller in which you want to perform calling:
override func viewWillAppear(_ animated: Bool) {
let appDelegate = UIApplication.shared.delegate as? AppDelegate
appDelegate?.window?.rootViewController = self
}
Unfortunately, none of the above solutions worked for me.
So Here is my solution.
Add this line
MobileRTC.shared().setMobileRTCRootController( self.navigationController)
=> When user taps of Join Call Button.
Make sure these conditions should meet as well.
ViewController which is used to open the ZOOM meeting should be a part of root navigation controller
DO NOT present modally the current Zoom Meeting Joining View Controller. Always push it to the root navigation controller.

Attempt to present UIAlertController on UITabBarController whose view is not in the window hierarchy

I have a timer in my app delegate, the user choose the time interval needed to execute the selector of that timer in another viewController that is not in the tab bar; after choosing the time interval a modal segue is used to load the first viewController in the UITabBarController
when the time interval passes I want the app to present an alert but it gives me this: Warning: Attempt to present UIAlertController on UITabBarController whose view is not in the window hierarchy!
here is the code that I'am using :
let alert = UIAlertController(title: "alert", message: "test", preferredStyle: .Alert)
self.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
First try to get visible view controller. You can do this with this public extension:
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
}
}
}
}
And then show alert controller in this vc

Presenting view controllers on detached view controllers is discouraged - screen view turns black

I have two view controllers: VC1 and VC2. From VC1, press a button will navigate app to VC2. VC2 has a network request function in viewDidLoad() which shows an alert if the request is failed.
If everything is fine, no request failure on VC2, when I move back to VC1, it would call to deinit function of VC2.
However, if the request failed and an error alert is shown, the deinit function (of VC2) wouldn't be called when I move back VC1. Moreover, it causes an error of "Presenting view controllers on detached view controllers is discouraged" while it's trying to show that alert after the screen displays VC1, screen then turns black except for navigation bar and the error alert of VC2 is shown on VC1 (The reason is when the VC2 is going to present the error alert, user suddenly press the back button on navigation bar to move back to previous screen). My alert is a global variable.
Here is the code I handle request and show alert on VC2:
func sendRegisterRequest() {
registerAPI.request(parameters: parameters) {
[weak self] (response) in
if let strongSelf = self {
strongSelf.handleResponse(response)
}
}
}
func handleResponse(response: Response<AnyObject, NSError>) {
let result = ResponseHandler.responseHandling(response)
if result.messageCode != MessageCode.Success {
// Show alert
handleResponseError(LocalizedStrings.registerFailedTitle, message: result.messageInfo, requestType: .Register)
return
}
}
func handleResponseError(title: String, message: String?, requestType: RequestType?) {
alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
let action = UIAlertAction(title: LocalizedStrings.okButton, style: UIAlertActionStyle.Default) { (action) -> Void in
self.handleAlertViewAction(requestType)
}
alert.addAction(action)
dispatch_async(dispatch_get_main_queue(), {
self.presentViewController(self.alert, animated: true, completion: nil)
})
}
I attach the screenshot here:
Could anyone have solution for this issue? Any help could be appreciated,
Lucy Nguyen.
I've got the same problem when I build my app. To solve this problem, I tried many changes and finally removed the error message.
I made an alert window in first VC to give user some notice. And I write the alert control code in - (void)viewDidLoad. I think you did it same or in - (void)viewWillAppear.
Just move your alert code to - (void)viewDidAppear. Then, error will be gone.

Displaying a UIAlertController in GameScene (SpriteKit/Swift)

The simple way to display a UIAlertView, in swift is:
let alert = UIAlertView()
alert.title = "Alert!"
alert.message = "A wise message"
alert.addButtonWithTitle("Ok, thank you")
alert.show()
But this is now depreciated in iOS 9 and recommends using UIAlertController:
let myAlert: UIAlertController = UIAlertController(title: "Alert!", message: "Oh! Fancy", preferredStyle: .Alert)
myAlert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
self.presentViewController(myAlert, animated: true, completion: nil)
Which is great, but I'm using SpriteKit and inside a GameScene, which gives an error of Value of type 'GameScene' has no member 'presentViewController'...
Do I need to switch back to my ViewController to present this or is there a way to call it from a GameScene.
I found THIS answer, but it's Objective-C.
There are many ways to handle this situation, I do not recommend Jozemite Apps answer, because this will cause problems on apps with more than 1 view controller.(you want to present the alert on the current view controller, not the root)
My preferred way of doing it is through delegation.
What needs to be done is create a protocol to handle messaging:
import Foundation
protocol ViewControllerDelegate
{
func sendMessage(message:String);
}
In your view controller:
class ViewController : UIViewController, ViewControllerDelegate
{
...
func sendMessage(message:String)
{
//do alert view code here
}
//in the view controllers view did load event
func viewDidLoad()
{
var view = self.view as! GameSceneView
view.delegate = self
}
In your view code:
var delegate : ViewControllerDelegate
Finally in game scene where you want to present:
self.view.delegate?.sendMessage(message)
This way allows limited access to the VC, and can be modified with more options when needed.
Another way is to set up a notification system, and use NSNotificationCenter to pass a message from the scene to the current VC and have it send a message;
in ViewController
func viewDidLoad()
{
NSNotificationCenter.defaultCenter().addObserver(self,selector:"AlertMessage:",name:"AlertMessage",object:nil);
}
func AlertMessage(notification:NSNotification)
{
if(let userInfo = notification.userInfo)
{
let message = userInfo["message"]
....//do alert view call here
}
}
In Game scene code:
...at the spot you want to send a message
let userInfo = ["message":message];
NSNotificationCenter.defaultCenter.postNotificationNamed("AlertMessage",object:nil,userInfo:userInfo)
Another approach is to save the view controller pointer to game scene view:
//in Game Scene View code
var viewController : UIViewController;
//in the view controllers view did load event
func viewDidLoad()
{
var view = self.view as! GameSceneView
view.viewController = self
}
//finally in game scene where you want to present
let myAlert: UIAlertController = UIAlertController(title: "Alert!", message: "Oh! Fancy", preferredStyle: .Alert)
myAlert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
self.view.viewController.presentViewController(myAlert, animated: true, completion: nil)
Yet another way is to make your view controller global.
In view controller code:
private var _instance : UIViewController
class ViewController : UIViewController
{
class var instance
{
get
{
return _instance;
}
}
func viewDidLoad()
{
_instance = self;
}
}
Then just call
ViewController.instance!.
whenever you need access to your view controller.
Each of these methods have there strengths and weaknesses, so choose whatever way works best for you.
Try using this. I work in SpriteKit and I used this code for my in app purchase messages in my game, Chomp'd.
self.view?.window?.rootViewController?.presentViewController(myAlert, animated: true, completion: nil)
The answer from #Knight0fDragon is nice, but I think it's a little long.
I will just put here another solution using Podfile of Cocoapoad for new comers (others guys having the same problem).
first you need to install cocoapod and initiate it for your project (very easy to do; check some YouTube video or this link.
in your obtained Podfile, copy, and paste this: pod 'SIAlertView'. It is "A UIAlertView replacement with block syntax and fancy transition styles". (More details here. Please give credit to the libraries Authors you're using.
Finally, in your GameScene.swift file add the following after the import or after the closing bracket of the class GameScene
private extension GameScene {
func showPauseAlert() {
let alertView = SIAlertView(title: "Edess!!", andMessage: "Congratulations! test testing bla bla bla")
alertView.addButtonWithTitle("OK", type: .Default) { (alertView) -> Void in
print("ok was pushed")
}
alertView.show()
}
}
You can add many button with title if you want, and do whatever action you want. Here I just print "Ok was pushed".
The above cited links plus this one helped me to understand and work with this UIAlertView easily in my multiple-level game, to display alert on the "Pause" button of the game.

Resources