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
Related
what im trying to accomplish is on pressing the statusbar a alert pops up. im trying to learn tweak development and the tutorial im following is a bit old and uses the deprecated UIAlertView So after finding the correct header (UIAlertController) i get the following errors trying to compile the tweak. Sry if this is a noob question i googled and nowhere really gave a clear answer for it. thanks in advance.
code-
#include <UIKit/UIKit.h>
%hook SBStatusBarManager
-(void)handleStatusBarTapWithEvent:(id)arg1{
UIAlertController *alert = [[UIAlertController alloc] alertControllerWithTitle:#"My Alert" message:#"I hope this works!" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}];
[alert addAction:defaultAction];
[self presentViewController:alert animated:YES completion:nil];
%orig;
}
%end
Here are the errors i got trying to compile.
Tweak.x:9:55: error: no visible #interface for 'UIAlertController' declares the selector 'alertControllerWithTitle:message:preferredStyle:'
Tweak.x:13:7: error: no visible #interface for 'SBStatusBarManager' declares the selector 'presentViewController:animated:completion:'
Firstly:
alertControllerWithTitle:message:preferredStyle is a class method, so you cannot call it on an instance.
This should work:
UIAlertController* alertController = [UIAlertController alertControllerWithTitle:#"My Alert"
message:#"I hope this works!"
preferredStyle:UIAlertControllerStyleAlert]
Secondly:
SBStatusBarManager is not a UIViewController, so you need to find a suitable UIViewController that can present your alert controller.
Maybe you can try to access the root view controller, this link should help: https://stackoverflow.com/a/36879892/3227743
Then, you can present the alert view controller accordingly.
I'm trying to first have a user enter credit card information and then agree to terms and conditions. Currently, the app is forcing a user to check terms and conditions and then enter credit card information. What is a better method or better way to do this? I am not strong in Objective C.
-(void)textFieldDidBeginEditing:(UITextField *)textField{
if(_agreedToTerms){
if(!_canceledScan){
CardIOPaymentViewController *cardV = [[CardIOPaymentViewController alloc]initWithPaymentDelegate:self];
[cardV setCollectCVV:NO];
//CardIOPaymentViewController *cardV = [[CardIOPaymentViewController alloc]initWithPaymentDelegate:self];
[cardV setCollectExpiry:YES];
// [cardV setCollectPostalCode:YES];
[self presentViewController:cardV animated:YES completion:nil];
}
}else{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"Error" message:#"Please agree to terms" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action)
{
}]];
[self presentViewController:alertController animated:YES completion:nil];
}
}
What is a better method or better way to do this?
You haven't shown us enough code to make a specific recommendation, but in general if you want things to happen in a different order, then you need to change the order in which the code makes those things happen. For example, let's say you have a view controller that asks users to accept terms and conditions and another that lets them enter credit card info. The order in which those view controllers are presented to the user is entirely up to you, and swapping them should be a pretty simple matter: you'll either re-arrange them in the storyboard, or you'll swap their positions in the code that presents them.
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.
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.
I have an application with an alertview that shows up when the application launches to explain about the app function.
On my alertView I want to make "Don't show again" button, so the user doesn't have to see the same alert every time he/she uses my app.
So how can I stop the alertView from showing up after the user selects this button. Should I work on the appDelegate or should I work on the viewcontroller where my alertview will pop up?
I would store a value in NSDefault for this, as we cannot change this on UIAlertView.
So once the UIAlertView has shown, set this value to something that represents "read", retrieve it in one of the App Delegate methods such as applicationDidBecomeActive and use it as a condition in a if statement for displaying the UIAlertView.
Let say you had an NSInteger = 0, which signifies "unread", once the UIAlertView is shown, set it to 1 and store it in NSDefault.
if(alertHasBeenRead == 0)
{
//bring up alert view
}
And subsequently in one of the delegate callback, set this value to something else other than 0, for example.
Hope this helps.
Try this:
if (![#"1" isEqualToString:[[NSUserDefaults standardUserDefaults] objectForKey:#"alert"]]){
UIAlertController *alert = [UIAlertController
alertControllerWithTitle:NSLocalizedString(#"Information", #"The title of an alert.")
message:NSLocalizedString(#"Some text goes here.", #"The message of an alert.")
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *ok = [UIAlertAction
actionWithTitle:NSLocalizedString(#"OK", #"A common affirmative action title, like 'OK' in english.")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
}];
UIAlertAction *dontshowagain = [UIAlertAction
actionWithTitle:NSLocalizedString(#"Don't Show Again", #"A common decline action title, like 'NO' in english.")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[[NSUserDefaults standardUserDefaults] setValue:#"1" forKey:#"alert"];
[[NSUserDefaults standardUserDefaults] synchronize];
}];
[alert addAction:ok];
[alert addAction:dontshowagain];
[self presentViewController:alert animated:YES completion:nil];
Let me know if this helps!