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.
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.
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).
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
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.
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.