UIAlertController title and message do not appear in Xcode 8.2 - ios

The YES and NO buttons function work as expected, the only problem is that the question No GPS hardware use Triangulation? does not appear to inform the user what the alert is about. The application is tabbed.
The entire code for the project including the xcode project files and Info.plist files can be
found on GitHub, in case you want to build or debug it.
The title and message of the UIAlertController do not appear for the following UIAlertController:
- (UIAlertController*) alertUserNoGPSHardware {
UIAlertController *alertToPresent = nil;
NSString* alertTitleString = #"GPS Alert";
NSString* alertMessage = #"No GPS hardware use Triangulation?";
if (!hardwareExistsOnDevice && mustUseGPSHardware) {
alertToPresent = [UIAlertController alertControllerWithTitle: alertTitleString message:alertMessage
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* yesButton = [UIAlertAction actionWithTitle:#"YES" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {mustUseGPSHardware = NO;}];
[alertToPresent addAction:yesButton];
UIAlertAction* noButton = [UIAlertAction actionWithTitle:#"NO" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {mustUseGPSHardware = YES;}];
[alertToPresent addAction:noButton];
}
return alertToPresent;
}
I've also tried to embed the above code in the function that calls this library routine. The same problem occurs.
- (void) setLabelWithGPSLatitudeAndLongitudeWithTimeStampData {
NSString *actionString = nil;
if (displayDataModel) {
if (isFirstGpsClick) {
// Call to the DataModel library that receives a pointer UIAlertView object from the GPS library implementation
// If the UIAlertView pointer is nil proceed with the displaying the latitude, longitude and timestamp.
// If the UIAlertView has a value show the alert, the alert should contain a function to update data in the GPS model.
// This will enable the user to approve of using WiFi or Radio triangulation when the GPS is not available.
/*
* BUG - title and message are not appearing in the alert, the buttons in the alert work as expected
* clicking the YES button removes the warning message that there is no GPS hardware and just
* returns the data. Clicking the no message displays displays the warning message every time.
*/
isFirstGpsClick = NO;
UIAlertController* gpsAlert = [displayDataModel provideGPSAlerters];
if (gpsAlert) {
[self presentViewController:gpsAlert animated:NO completion:nil];
return;
}
}
actionString = [displayDataModel provideGPSLocationData];
}
else {
actionString = #"GPS Button Action Failure: Data Model not created";
}
[displayButtonAction setText:actionString];
}
I've also tried moving the embedded code into the following 2 functions
- (void)viewWillLayoutSubviews {
/*
* Get the tab bar height from the Application Delegate so that the total vertical space
* can be calculated.
*/
AppDelegate* appDelegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
if (appDelegate) {
UITabBarController *TempTabBar = appDelegate.tabBarController;
if (TempTabBar) {
// Tab Bar Height is larger than myDelegate.tabBarController.tabBar.frame.size.height indicates
tabBarHeight = TempTabBar.tabBar.frame.size.height * 2.5;
}
}
[self setSubViewSizeVariablesBasedOnViewBounds];
[self addButtonAndLabels];
self.view.backgroundColor = [UIColor whiteColor];
}
- (void)viewDidLoad {
[super viewDidLoad];
if (self.displayModelLibraryInitialization) {
NSLog(#"In Objective-C Implementation viewDidLoad - unable to initialize displayModelLibrary");
}
}
When I move the UIAlertController into viewWillLayoutSubviews() or viewDidLoad() I get the black screen, not
the alert and not the buttons and labels that should be there.
This question does not apply because the current problem is in Objective-c and not Swift.
This question does not apply because no textfield is getting updated.
The code does not use alert builder so this question doesn't apply.
Background
I am new to programming in Xcode, iOS, Objective-c and Swift. This is my first iOS project. It was
an interview coding challenge.
OSX - El Capitan
Xcode - Version 8.2 (8C38)
Running in the simulator.

I am only answering this so that it doesn't add to StackOverflow unanswered questions (on CodeReview we would call an unaswered question a zombie).
#DavidH helped provide the answer.
It seems that there are a few minor quirks to the iPhone 7 plus simulator in Xcode 8.2. DavidH was able to see the message in the alert in the iPhone 7 simulator in Xcode 8.3. I switched to the iPhone 7 simulator from the iPhone 7 plus simulator and saw the message in the alert.
This indicates there may not have been a problem in the code and that the iPhone 7 plus simulator in Xcode 8.2 may be buggy.

Related

How can I programmatically dismiss a UIAlertController

I need to programmatically dismiss a UIAlertController that I'm using as a "please wait" message. I can present the alert without problem but when it comes to dismissing the alert, 50% of the time it dismisses and the other 50% it doesn't, forcing me to restart the app just to continue using it. Any ideas how to dismiss the alert with 100% consistency?
//loadingAlert is a UIAlertController declared in the .h file
//present the Alert
loadingAlert = [UIAlertController alertControllerWithTitle:#"Loading..." message:#"Please wait while we fetch locations" preferredStyle:UIAlertControllerStyleAlert];
[self presentViewController:loadingAlert animated:YES completion:nil];
//parse JSON file
_listOfAcquisitions = nil;
MNSHOW_NETWORK_ACTIVITY(YES);
NSString *WebServiceURL = [NSString stringWithFormat:#"JSON URL", _search];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSDictionary *dictionary = [JSONHelper loadJSONDataFromURL:WebServiceURL];
dispatch_async(dispatch_get_main_queue(), ^{
_listOfAcquisitions = [NSMutableArray array];
for (NSDictionary *oneEntry in dictionary) {
Acquisitions *acqu = [[Acquisitions alloc] init];
if([oneEntry objectForKey:#"ADDRESS1"] == (NSString *)[NSNull null]){acqu.ADDRESS1 = #"";}
else {acqu.ADDRESS1 = [oneEntry objectForKey:#"ADDRESS1"];}
if([oneEntry objectForKey:#"STATEABBR"] == (NSString *)[NSNull null]){acqu.STATEABBR = #"";}
else {acqu.STATEABBR = [oneEntry objectForKey:#"STATEABBR"];}
if([oneEntry objectForKey:#"TOWN"] == (NSString *)[NSNull null]){acqu.TOWN = #"";}
else {acqu.TOWN = [oneEntry objectForKey:#"TOWN"];}
if([oneEntry objectForKey:#"ZIPCODE"] == (NSString *)[NSNull null]){acqu.ZIPCODE = #"";}
else {acqu.ZIPCODE = [oneEntry objectForKey:#"ZIPCODE"];}
[_listOfAcquisitions addObject:acqu];
}
dispatch_async(dispatch_get_main_queue(), ^{
MNSHOW_NETWORK_ACTIVITY(NO);
[self refreshAnnotations:self];
});
});
});
//finally dismiss the alert...
[loadingAlert dismissViewControllerAnimated:YES completion:nil];
}
I've just been learning how to do this.
So, wherever the alert controller is built, you need to add the action button either to "OK" in default style or "Cancel" in cancel style.
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:#"You made a mistake."
message:#"Pray we don't alter the alert further"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okayAction = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[alertController dismissViewControllerAnimated:YES completion:nil];
}];
[alertController addAction:okayAction];
[self presentViewController:alertController animated:YES completion:nil];
There are other UIAlertActionStyle enumerations, such as UIAlertActionStyleCancel which will put a separator space between other actions and UIAlertActionStyleDestructive which will make the font red but will be in line with other UIAlertActions.
Make sure you add in order: standard actions (Okay, Open Camera, Photo Library) and THEN cancel actions.
There's also preferredStyle:UIAlertControllerStyleActionSheet which is used to set up options for the user. I use this to show Camera and Photo Library.
Also, for your specific dismiss action. The reason it's not working is because you are attempting to dismiss it on the background thread. You should ALWAYS dismiss or make UI changes on the foreground thread. This will cause an NSException/crash in the future.
dispatch_async(dispatch_get_main_queue(), ^{
// dismiss your UIAlertController
});
This is how you should be doing it with your specific code:
dispatch_async(dispatch_get_main_queue(), ^{
MNSHOW_NETWORK_ACTIVITY(NO);
[self refreshAnnotations:self];
[loadingAlert dismissViewControllerAnimated:YES completion:nil];
});
});
You have other issues with your code that you should ask others for help about. I am only a junior developer so I'm not sure how to correctly do what you're trying to do but this should help with dismissing.
You might want to look into loading wheels or toast messages that will say "Please wait" or "Loading".
Using a UIAlertController to show a loading message is rather bad taste.
First of all your network call should probably happen in the completion block of the presentViewController. So you don't dismiss it before it has appeared.
Also the nested dispatch_async seems off, since you call dispatch_get_main_queue(line 32) while already within the mainQue(line 13). And if this were to work the dismiss would need to be within the dispatch_async block so that it actually would dismiss.
But more importantly this is kind of a misuse of the UIAlertController API. Those are intended for user input not to Block UI.
You are better of implementing your own custom view subclass.
Or using MBProgressHUD (https://github.com/jdg/MBProgressHUD). There you can use either the MBProgressHUDModeIndeterminate or MBProgressHUDModeText to accomplish what you are trying.
You're creating/starting/dismissing the alert all within the same block, which means both presentViewController:loadingAlert and [loadingAlert dismissViewControllerAnimated: are being called in the same runloop. This is why you're getting unexpected results.
The dismiss needs to be called from a different cycle of the runloop, so having it called in a separate *block is what you want. You're already doing things in different threads using dispatch_async, which execute in discretely separate runloop so the solution for you is to put the dismissViewControllerAnimated: call within the dispatch_async(dispatch_get_main_queue() to ensure that its both called on the main thread for UI updates, and called in a separate run-loop as its presentation.
You could use dismissWithClickedButtonIndex:animated: but it's now deprecated in iOS9. I would use a timer:
// 3 second delay
[NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(dismissAlert) userInfo:nil repeats:NO];
- (void) dismissAlert {
[loadingAlert dismissWithClickedButtonIndex:0 animated:YES];
}
Of course, this isn't what UIAlertViews are intended for. You'd be better of using a loading spinner somewhere (UIActivityIndicatorView).

iOS Voice Over only reads out the title of any alert views

My accessibility work on my app continues. The next issue I've discovered is that whenever an alertView appears, voice over only reads out the following
Alert
Alert Title
Even though I believe it's meant to read out the Alert Body as well.
To work around this issue I've had to do the following code
NSString *alertAction = notification.alertAction;
NSString *alertBody = notification.alertBody;
if (UIAccessibilityIsVoiceOverRunning())
{
// TODO - iOS VoiceOver has a bug where it only reads out the alert action, not the body.. combine everything into one
// for now so its read out together
alertAction = [NSString stringWithFormat:#"%#, %#", alertAction, alertBody];
alertBody = nil;
}
UIAlertController* alertController = [UIAlertController alertControllerWithTitle:alertAction
message:alertBody
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:#"Ok" style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {
}]];
[visibleViewController presentViewController:alertController animated:YES completion:nil];
To combine the title and message into one string which I use for the title. Clearing out the message.
This does seem to fix the problem, but it feels a bit clunky and obviously looks a little objectionable with so much text in the bold title font.
Anyone come across this issue, or got any other fixes to avoid having to butcher all my alerts this way?
Cheers

Why do my UIAlertActions become nil on iPad Air

I'm working on an old iOS app originally written for iOS 6, and it had some UIActionSheets that needed to be changed, so I've been working to move them over to UIAlertControllers, using UIAlertActions. This has worked perfectly fine on a iPad2 Simulator, however when testing on an iPad Air (the only iPad I have access to) my UIAlertAction, and UIAlertController become nil directly after being created (looked at in the debugger, it receives a pointer on creation, however as soon as it executes the next line it becomes null). Here's a code sample:
//When hovering over the next line, alert has a pointer
UIAlertController* alert = [UIAlertController
alertControllerWithTitle:#"info"
message:#"testmessage"
preferredStyle:UIAlertControllerStyleActionSheet];
//alert pointer is now nil
//test pointer shows up
UIAlertAction* test= [UIAlertAction
actionWithTitle:#"I'm a Button"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action){
[alert dismissViewControllerAnimated: YES completion:nil];
}
];
//test pointer is nil, test2 pointer exists
UIAlertAction* test2 = [UIAlertAction
actionWithTitle:#"I'm a Button"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action){
[alert dismissViewControllerAnimated: YES completion:nil];
}
];
//test2 pointer is nil
[alert addAction:test];
[self presentViewController:alert animated:YES completion:nil]; //app crashes, because I'm trying to present a nil modal.
Any thoughts or help would be much appreaciated!
UIAlertController is for iOS8 upwards. Use UIAlertView and UIActionSheet for prior to iOS8
You are best to check wether your device responds to the class,
if ([UIAlertController class]) {
// use UIAlertController for the action sheets as you have already posted
// For all operating systems (like iOS8 upwards) will land in here.
} else {
// use UIActionSheet - like you already used for iOS6
}
It's not wise to check the operating system deployment number, like if 8.0 etc, checking the if it responds to the class is the proper way to do it.
It prevents a crash means you're not relying on float numbers which are not guaranteed to be reliable, as if they change the way the operating systems is named in future, your code would crash.

Is there any way to encapsulate UIAlertController within another UIViewController?

I'm fairly new to iOS, so please keep answers clear. I've been toying with encapsulating UIAlertController in another UIViewController to use it as a replacement for UIActionSheet as it's deprecated in iOS 8.
The idea would be a replacement for UIActionSheet that is backward and forward compatible, , call it ImmortalActionSheet for instance.
If the component is used in iOS 8 or greater, it can use UIAlertController, otherwise it would fall back to UIActionSheet. That would make it a backward and forward compatible action sheet to replace many action sheets around an application I'm working with. And yes I need to maintain backward compatibility.
I've prototyped this but for whatever reason, when I present ImmortalActionSheet, the UIAlertController view itself will always show up at the top left (0, 0). No matter if I change the center, it never moves.
-(void) loadView
{
UIView * child = nil;
if ( [UIAlertController class] )
{
UIAlertController * alertController = [UIAlertController alertControllerWithTitle:#"Make Choice!!" message:#"Choose one!" preferredStyle:UIAlertControllerStyleActionSheet];
for (UIAlertAction * action in actions ){
[alertController addAction:action];
}
child = alertController.view;
[self addChildViewController:alertController];
}
self.view = [[UIView alloc] init];
self.view.backgroundColor = [UIColor clearColor];
[self.view addSubview:child];
}

UI gets stuck on iOS 7 while dismissing an Alert displayed from didSelectRowAtIndexPath on iPad

I have a piece of code that, when a row in a table view is selected, will display an alert and wait until that alert is dismissed. While it works fine on iPads with iOS 5 and 6, on iOS 7 it gets stuck while trying to dismiss the alert.
To illustrate the issue I created a simple master-detail app and created a simple MyAlert class that extends UIAlertView and conforms to UIAlertViewDelegate:
#interface MyAlert : NSObject <UIAlertViewDelegate>
{
volatile BOOL completed;
UIAlertView * alert;
}
- (void) showAndWaitUntilDone:(NSString*)message;
#end
MyAlert.m:
#implementation MyAlert
- (void) showAndWaitUntilDone:(NSString*)message
{
alert = [[UIAlertView alloc] initWithTitle:#"Alert"
message:message
delegate:nil
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Other", nil];
if (alert)
{
alert.delegate = self;
[self showAndWaitUnitlDone];
}
}
- (void) showAndWaitUnitlDone
{
completed = NO;
[alert show];
while (!completed)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
}
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
completed = YES;
}
#end
I then display the alert in my ViewController like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[MyAlert showAndWaitUntilDone:#"test msg"];
}
The effect:
Like mentioned before, this works OK on iOS 5 and 6.
If I don't block the main thread, everything seems to work OK, however, if I run this code e.g. from UIButton callback it works like a charm.
There seems to be a bug in iOS 7 which causes that UIAlertView delegate will never be called once the RunLoop is started. It acts exactly the same when the thread waiting for the UIAlertView is not the main thread. People suggest using UIAlertView with blocks or writing a custom Alert View.
For more details see Apple's developer forum thread: https://devforums.apple.com/message/887792
Maybe it's just me, but this seems like bad design in the first place. You're locking up the main thread for an unknown length of time.
Why not simply have the didSelectRowAtIndexPath display the alert, and have the alert callback (alertView:clickedButtonAtIndex:) do the remaining work you were waiting on (or call a function which will do it)? If need be, use a variable to store which entry was tapped... This would prevent the negative aspects of tying up the main thread while still giving what appears to be your desired effect.
It wouldn't surprise me if apple implemented something that prevents app designers from locking up the main thread (which is why your code will only work if waitUntilDone: is set to NO, essentially putting the code on an async call).

Resources