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.
Related
Using firebase to create user sign-in as follow,
a) Sign-up view -> b) Email view -> c) userName view
And have two button namely back and next, all of this can be seen below,
Problem occurs when view b has previously visited view c and upon clicking back button on view b, Firebase falsely goes into user auth function (checked using breakpoints),
Supporting code for back button,
(IBAction)backButtonPressed:(id)sender {
if(page==0)
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Signup" message:#"Are you sure you want to quit signup?" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *ok = [UIAlertAction actionWithTitle:#"Yes" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action)
{
[self.navigationController popViewControllerAnimated:YES];
//BUTTON OK CLICK EVENT
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:#"Cancel" style:UIAlertActionStyleDestructive handler:nil];
[alert addAction:cancel];
[alert addAction:ok];
[self presentViewController:alert animated:YES completion:nil];
}
else
{
[self moveToPreviousStep];
}
if (page==3){
self.skipButton.hidden=YES;
}
}
Supporting code for next button with Firebase specific code,
[ARSLineProgress showWithPresentCompetionBlock:^{
[[FIRAuth auth] fetchProvidersForEmail:self.signUpEmail.text completion:^(NSArray<NSString *> * _Nullable providers, NSError * _Nullable error) {
[ARSLineProgress hideWithCompletionBlock:^{
if([providers count]==0)
{
NSLog(#"email is valid for signup");
signingUpFromEmail = TRUE;
[self moveToNextStep];
}
What should happen,
Upon clicking the back button in view b it should go into view a without getting into Firebase auth function, (maybe it's a pending thread for firebase that needs to purged or it can be handled programatically)
Turns out there were two actions assigned to the back button i.e the MoveNextPage and MoveBackPage in storyboard (rookie mistake as the next button was copied and pasted to back button position in storyboard), so the removal of next button action from storyboard corrected the problem.
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.
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
In ios8 delegate methods of UIActionSheet calling multiple times
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
I have checked in ipad 2 with IOS 8
Its a bug of the ios 8..
As the #rob said UIActionSheet is deprecated in iOS 8. (UIActionSheetDelegate is also deprecated.)
To create and manage action sheets in iOS 8 and later, use UIAlertController
This is indeed the current behavior that I've seen reported in several places. It does appear to be a bug and hopefully will be fixed soon but no way to know for sure.
You should UIAlertController to replace all AlertView and ActionSheet in iOS8.
If your target lower than iOS8, then you should check version and add more code
For example, this code is for iOS8 ActionSheet
-(void)testActionSheet{
UIAlertController* alertAS = [UIAlertController alertControllerWithTitle:#"Test ActionSheet"
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#"Action" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
NSLog(#"Action");
}];
[alertAS addAction:defaultAction];
UIAlertAction *cancleAction = [UIAlertAction actionWithTitle:#"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
}];
[alertAS addAction:cancleAction];
[self presentViewController:alertAS animated:YES completion:nil];
}
If you want to continue using the existing API, there is a bit of behavior that lets you know when to run your code and when to ignore the delegate call - the buttonIndex.
The first time the delegate methods are called, they are always passed the correct buttonIndex. The second time through, however, they are called with a buttonIndex of the cancel button.
Unfortunately UIActionSheet is deprecated in iOS 8:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIActionSheet_Class/
I had the same issue. One workaround: Use actionSheet.tag. Set it to a valid number (it will be 0 by default) such as 1, 2, ... while instantiating. Handle the response in:
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
Check if it is valid (ex: not -1 before handling it). Once the response is handled here, before returning, set:
actionSheet.tag = -1;
This ensures that you will ignore it, although the second call is made. This works in my case.
I would like to add: UIAlertController is the correct way to now to use UIAlertViews in iOS8.0+ (as UIAlertView is deprecated) however the bug of being able to select multiple options is somewhat mitigated.
The separate options in the alert view CAN be selected/highlighted at the same time, but the delegate method only seems to fire off one of them. Which one actually triggers off is undetermined, but I can confirm only one is fired off despite two/or more being highlighted if you use multiple fingers.
I have an issue related to UIAlertView while running our app on iOS 8.
I am showing an alert with title as nil. It was working fine in iOS 7 but now UI looks odd.
I have attached screenshot here.
One solution I found is that when I provide empty string #“” it looks okay. See below screenshot. But I am not sure if the issue I mentioned is bug in beta iOS 8 version or if there is any other better solution. Even with the solution it's not exact as it was in iOS 7.
iOS 7 - showing alert view with title as nil. Screenshot here.
The closest I could get with iOS 8 was by setting the title instead of the message:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Location field required." message:nil delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
It should be noted, however, that UIAlertView is deprecated in iOS 8 and, if you're going to be using separate code paths for iOS 7 and iOS 8, you should be using UIAlertController instead:
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Location field required." message:nil preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self dismissViewControllerAnimated:YES completion:nil];
}]];
[self presentViewController:alert animated:YES completion:nil];
I got the same results with both methods.
It has been the best practice for me to use initWithTitle:#"" for UIAlertView, UIActionSheet since iOS 6 because I was facing a design issue during that time when I was using initWithTitle:nil. I tried to find back, I couldn't find it what exactly is the reason.
From your screen shot on iOS 8, I think there is a change of view hierarchy on UIAlertView for iOS 8. I think Auto layout might be implemented on the view hierarachy as well as you can see the messageLabel jump up to the titleLabel.
I can not be sure because the view hierarchy for UIAlertView is private.
The UIAlertView class is intended to be used as-is and does not
support subclassing. The view hierarchy for this class is private and
must not be modified.
See: https://developer.apple.com/library/ios/documentation/uikit/reference/UIAlertView_Class/UIAlertView/UIAlertView.html
But, I use the code:-
NSLog(#"%#",[self.alertView description]);
Result on iOS 7.1:
<UIAlertView: 0x7fb3c05535b0; frame = (18 263; 284 62); opaque = NO; layer = <CALayer: 0x7fb3c0519810>>
Result on iOS 8.0:
<UIAlertView: 0x7bf64840; frame = (0 0; 0 0); layer = <CALayer: 0x7bf648f0>>
I am not sure why the UIAlertView frame for iOS 8 is (0 0; 0 0);
Like Mike said, I think you should learn to use UIAlertController for iOS 8.
I managed to get decent message alignment without the bold font by:
Setting the title to #"" instead of nil, and
(If IOS8) prepend a "\n" in front of the message.
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#""
message:#"\nLocation field required."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
Please try this code.
it is working on my side
xcode version 9.2
UIAlertController * alert= [UIAlertController
alertControllerWithTitle:nil
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction* btn1 = [UIAlertAction
actionWithTitle:#"OKAY"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
}];
[self presentViewController:alert animated:YES completion:nil];