How to prevent multiple UIAlertView from stacking up? - ios

I'm using MPMoviePlayerController in iOS. I'm listening on any errors it may have while playing videos. In my error handlers, I pop up an UIAlertView. Sometimes errors may occur in quick succession of each other and thus multiple alert boxes will stack up. For a better user experience, I wish to not pop up another alert if an earlier one is still displayed.

Try this:
Set a boolean to true when you pop up an alert, set it to false when you close an alert, and always check the boolean to see if it's true before you pop up an event. If it is true, you'll know you have an alert window already showing.
You can find this solution and some other discussion here.

You can implement this yourself trivially. Since you're displaying the alert, and you're also the alert's delegate so you know when it's gone, you can easily track whether there's an alert visible just by setting a boolean flag upon alert show and alert hide. That way if the boolean is set, you can quash any subsequent alerts.

When an alert appears, it will be moved to a _UIAlertOverlayWindow. Therefore, a pretty fragile method is to iterate through all windows and check if there's any UIAlertView subviews.
-(BOOL)checkAlertViewVisibleStatus
{
for (UIWindow* window in [UIApplication sharedApplication].windows)
{
NSArray* subviews = window.subviews;
if ([subviews count] > 0)
if ([[subviews objectAtIndex:0] isKindOfClass:[UIAlertView class]])
return YES;
}
return NO;
}
This is undocumented as it depends on internal view hierarchy, although Apple cannot complain about this. A more reliable but even more undocumented method is to check if `
[_UIAlertManager visibleAlert]
` is nil.
These methods can't check if a UIAlertView from SpringBoard is shown.

As far as I know, the only way is to keep track of whether or not an alert is currently being displayed and/or one is currently being dismissed within your application. Try showing the alert in the appDelegate, and then using a notification to notify the appDelegate each time the alert is closed. This way the appDelegate keeps track of whether or not there is an alert with a boolean flag variable.

Use the new UIAlertViewController. If you try to present an alert while another is in view it ignores it and outputs the warning shown below. It's a nasty side affect for people who want the traditional behaviour of stacked alerts but for your case it's a good solution.
Warning: Attempt to present <UIAlertController: 0x7f9ef34c17e0> on <MasterViewController: 0x7f9ef344ec90> which is already presenting (null)

It should work:
-(BOOL) doesAlertViewExist
{
if ([[UIApplication sharedApplication].keyWindow isMemberOfClass:[UIWindow class]])
{
return NO;//AlertView does not exist on current window
}
return YES;//AlertView exist on current window
}

Related

UITextField endEditing:YES (force) Not Dismissing Keyboard

I have several UITextField objects in a custom Date/Time editor control (no spinners). The control is in a UITableViewCell on a modally presented TableViewController during a data record add operation.
If users cancel the add operation I need to discard all the data and dismiss the controller and the keyboard if it is showing. I do this by calling this method when the Cancel button is pressed:
-(void)cancel
{
_tempMOC = nil;
[self.view endEditing:YES];
[self dismissViewControllerAnimated:YES completion:NULL];
}
According to the Apple docs, for UIView endEditing which state:
Specify YES to force the first responder to resign, regardless of whether it wants to do so.
my textFieldShouldEndEditing: return value should be ignored, however this is not the case.
I have verified with log statements that textFieldShouldEndEditing: is getting called.
But, the value is not ignored because only when this method returns YES is the keyboard dismissed.
The result is that the modal view controller gets dismissed leaving an orphaned keyboard on the screen which can only be gotten rid of by restarting the app.
This is a problematic pain in the arse because I do various validation operations in here:
-(BOOL)textFieldShouldEndEditing:(UITextField *)tf
{
/* Validate data for up to 6 UITextFields in this control. */
...
return valid ? YES : NO;
}
In the case where the user hit cancel on the controller, I really need it to ignore the return value, else I have have to do some kind of stupid caveat to figure out that six levels up somewhere the user hit cancel and in that case always return YES.
Any thoughts on what might be going on or if I have missed something... or ugh, I've found yet another Apple bug?!
****** UPDATE ****
This is now reported as radar: 32442632.

The handler of a UIAlertAction is a bit too late - how can I make it immediate?

I am trying (...) to add a sound effect to the buttons added to a UIAlertController. I fire a sound effect in the handler, but this actually is a bit too late. The sound fires like 0.5 seconds too late. I want the sound to fire as soon as the alert is about to dismiss, not after it has dismissed. With UIAlertView this was possible to handle using alertWillDismiss... rather than alertDidDismiss.
Did I miss something?
No, you didn't miss anything. The functionality you're looking for is not provided by UIAlertController. Consider providing your own presented view controller, over which you'll have the kind of fine control you're after.
I used Patrick Goley's suggestion, namely to subclass UIAlertController and override viewWillDisappear. Worked great for me.
//
// ImmediateClickAlertController.swift
//
// This subclass of UIAlertController plays a click immediately whenever it is dismissed (i.e. when a button is tapped).
// This fixes an issue when trying to play a click in an attached UIAlertAction, which does not happen until after its view disappears.
import AudioToolbox
class ImmediateClickAlertController: UIAlertController {
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
// play a click
let pressKeySystemSoundID: SystemSoundID = 1104
AudioServicesPlaySystemSound(pressKeySystemSoundID)
}
}
A bit of a hackery, but perhaps you could:
Try and grab a reference to the alert's button (by traversing the -admittedly private- view hierarchy tree), and
Use KVO to detect any changes to its selected and/or highlighted properties (my own experience is that selected is not reliably observable, while highlighted is).
...but all of this is quite fragile, not elegant, maight break in a future release of the OS and/or get you rejected from the app store...?
So you're best option (even if the most laborious) is to roll your own modal view controller:
Apple's documentation on presenting modal view controllers.
Demo project I made following the above docs (a custom "UIAlertController look-alike" with an embedded UIActivityIndicator - for use during long, asynchronous processes):

How do I force a UIAlertView to be the proper size?

I have a UIAlertView that's appearing and blocking input, but the actual "OK/Cancel" is 0×0 at (0, 0), so it's not dismissible by the user. Below is my current fix, but I'd much rather be able to tell iOS "Hey! This isn't right. Make it right." So, is there any way for me to reposition or reset the position of a UIAlertView's frame in iOS?
if (_floatingAlert && // if there is an alert AND...
(_floatingAlert.visible || // ( the alert isn't visible OR...
_floatingAlert.frame.size.height + _floatingAlert.frame.size.width == 0)// The frame is invisibly small )
)
{
[_floatingAlert dismissWithClickedButtonIndex:0 animated:YES];
_floatingAlert = nil;
}
Uialertview and uiactionsheet are depreciated and though they still work for now you should consider using uialertcontroller.
I am currently working on a project where I came across a couple comments about what you are looking to do. One option would be to set your class as a uialertcontroller delegate which sounded like gave some control over customizing appearance. The other option would be to create your own view that you customize and present it to a user when you need to alert the user of something.

Is there a way to know that iOS keyboard is really hidden (dismissed by user)?

I need a way to detect the case when user dismisses iOS keyboard manually, using "keyboard" button on keyboard. I tried to use UIKeyboardDidHideNotification, but quickly discovered that this event is also fired when user splits the keyboard, leaving it on screen.
Is there a way to know for sure that keyboard was really hidden?
To get solution I had to slightly modify my original implementation: I've replaced assigning nil to inputView member of my main view with creating and destroying custom invisible UIView<UIKeyInput> view to show and hide keyboard correspondingly. This allowed me to override this view's resignFirstResponder method which is always called on keyboard resigning - either in normal or in split state, when user dismisses keyboard using special button or when I remove it programmatically.
I believe that UIKeyboardDidHideNotification is only sent when the keyboard is truly gone. From the Apple docs:
Posted immediately after the dismissal of the keyboard.
However, you could also look to see if any of your inputs are currently the first responder:
BOOL keyboardUp = NO;
for (UIView *view in self.textInputs)
{
if (view.isFirstResponder)
{
keyboardUp = YES;
break;
}
}

ios ignoring viewWillDisappear

I have a form on a tabbed view; I mark the form dirty of any of the fields have been changed and I want to pop up an ActionSheet with "save"/"cancel" if the form is dirty (in lieu of a "save" button). Is there any way to stop the view from disappearing (or being removed from the view stack) until the user responds to the ActionSheet being handled?
A couple of thoughts:
It's worth noting that Apple's Human Interface Guidelines (the HIG) explicitly discourages this practice. They suggest that apps should "ask people to save only when necessary" because people "should have confidence that their work is always preserved unless they explicitly cancel or delete it." Perhaps in your case, it's important to have this feature, but it's generally discouraged.
An alternative, if you want to give users the chance to revert to old settings is to provide an "undo" button, that way, you honor the HIG and effectively auto-save, but you also give the user to explicitly revert to prior values if they really need to.
As others have noted, the notion of prompting to save or discard on viewWillDisappear doesn't quite work. It's logically too late in the process. viewWillDisappear could be called for too many reasons, many of which are not under your control, and it's not copacetic to fail to return promptly to that method, to introduce new user interface elements, etc.
If you really, really need the "save" vs. "cancel" user interface, then that lends itself to more of a modal interface (or push a new view controller that you have to pop off to return to your tab bar view controller) with save and cancel buttons rather than a tab bar interface. E.g. your tab bar view could show current values, you tap on an "edit" button, which pushes new view with save and cancel buttons. We don't know enough about your app to be able to advise whether this is logical in your case or not. (For another approach, see enabling edit mode in view controller.)
You can't stop the view from disappearing once the app has progressed to the point where viewWillDisappear: has been called. The thing to do would be to create a function like:
- (void)saveAndClose {
//Display sheet asking user what they want to do
}
- (void)dismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated
{
if (buttonIndex == 0) // 0 or whatever the index of your save button is
{
// Perform save functions
}
[self dismissModalViewControllerAnimated:YES]; // or pop the view controller if appropriate
}

Resources