My app starts with a "home screen" navigation controller that sits in its own storyboard, and can segue to different storyboards, each beginning with a new navigation controller. One of my secondary storyboards has a paywall. When the user elects to dismiss the paywall without making a purchase, all I've been able to accomplish is dismissing the paywall and displaying the root view controller of the storyboard that requires a purchase. I'm trying to send the user back to the "home screen" where they can see other content or segue to a different storyboard.
[PaywallManager registerBlockingPaywallClosedHandler:^{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"Blocked Paywall Closed" message:#"Return to home screen without purchase." preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[alertController dismissViewControllerAnimated:true completion:^{
}]
}];
[alertController addAction:okAction];
UIViewController *rootViewController = [[[UIApplication sharedApplication] keyWindow] rootViewController];
[rootViewController presentViewController:alertController animated:true completion:^{}];
}];
When the user dismisses the UIAlertView by pressing the OK button, I need to segue back to the main navigation controller (home screen). Currently when the user dismisses the UIAlertView, they get access to the paid content. If I remove the closed handler, the app gets stuck on the paywall until a purchase is made or the user kills the app. Any help would be greatly appreciated. I am still struggling with Objective-C and haven't had time to learn Swift so please go easy on me.
I figured it out. The solution is here:
[PaywallManager registerBlockingPaywallClosedHandler:^{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"No Purchase Made" message:#"Return to the Home screen without purchase." preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self.navigationController popViewControllerAnimated:YES];
}];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil];
}];
Related
I am having a Catch 22 issue that I cannot get out of, I am using UIAlertCOntroller to show information to the user and based on the answer I need to take some actions
if([NWTillHelper finishTransactionWithoutEmail] != 1) {
if([NWTillHelper getCrmId] == nil) {
//Step 1: Create a UIAlertController
UIAlertController *userInfoCheck = [UIAlertController alertControllerWithTitle:#"No Customer Email!"
message: #"Do you want to proceed without customer email? No receipt will be sent out in this case!"
preferredStyle:UIAlertControllerStyleAlert];
//Step 2: Create a UIAlertAction that can be added to the alert
UIAlertAction *Yes = [UIAlertAction
actionWithTitle:#"Yes"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
NSUserDefaults *tillUserDefaults = [NSUserDefaults standardUserDefaults];
[tillUserDefaults setInteger:1 forKey:#"finishTransactionWithoutEmail"];
[tillUserDefaults synchronize];
[userInfoCheck dismissViewControllerAnimated:YES completion:nil];
}];
UIAlertAction *No = [UIAlertAction
actionWithTitle:#"No"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[userInfoCheck dismissViewControllerAnimated:YES completion:nil];
}];
//Step 3: Add the UIAlertAction ok that we just created to our AlertController
[userInfoCheck addAction: Yes];
[userInfoCheck addAction: No];
//Step 4: Present the alert to the user
[self presentViewController:userInfoCheck animated:YES completion:nil];
return;
}
}
The problem I am having is that the last return statement seems to be run BEFORE the completion blocks finishes and I want the return to be conditional on the user action, but if I put the return in the Yes alertAction then the code below in the method is run before the user has the chance to select yes/no so I am stuck I need the last return to stop the code below that to be run but at the same time I need to wait for the completion block to finish? how can I handle this situation so that my code below this entire code block is run only after the user selects the action?
Any time you see APIs that accept "handler" blocks, it's best to assume they run asynchronously. That means that the order of statements in your source code is not the order of operations.
In this case...
presentViewController returns immediately (and your function returns immediately thereafter), causing the alert to show.
The app continues to spin its main run loop while the user is sitting there looking at the alert box. UIKit tracks touches (even if they have no visible effect), other elements of your UI might be animating visibly behind the alert or reacting to non-user inputs like network activity (which may or may not involve other code of your own).
The user taps a button in the alert box, causing UIKit to run either your Yes or No completion handler.
This means that whatever logic you want to use for responding to the user's alert choice cannot be written in the method body that presents the alert. You'll need to arrange for such code to run as a result of your alert action handlers. A pseudocode-ish example:
- (void)handleEmailChoice:(BOOL)proceedWithoutEmail {
// do whatever depends on user choice
}
// elsewhere
UIAlertController *userInfoCheck = [UIAlertController alertControllerWithTitle:/*...*/];
UIAlertAction *yes = [UIAlertAction actionWithTitle:#"Yes"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
// set defaults, dismiss alert, then:
[self handleEmailChoice:YES];
}];
UIAlertAction *no = [UIAlertAction actionWithTitle:#"No"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
// dismiss alert, then:
[self handleEmailChoice:NO];
}];
[userInfoCheck addAction: yes];
[userInfoCheck addAction: no];
[self presentViewController:userInfoCheck animated:YES completion:nil];
You can try to attach it to the root view controller, e.g.:
//Step 4: Present the alert to the user
UIViewController *controller = [UIApplication sharedApplication].delegate.window.rootViewController;
[controller presentViewController:userInfoCheck animated:YES completion:nil];
return;
Does anyone know how to enable a UIAlertViewController which has been presented to be dismissed by clicking the "Menu" button on tvOS?
The Settings app on the Apple TV 4 features that behavior but it doesn't work by default in my app. I use the following code to create the actions the user can take, but would like to allow him not to chose anything and go back by pressing the "Menu" button on the remote.
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#"Alert"
message:#"Please make a choice"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* action1 = [UIAlertAction actionWithTitle:#"Option 1" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}];
[alert addAction:action1];
UIAlertAction* action2 = [UIAlertAction actionWithTitle:#"Option 2" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}];
[alert addAction:action2];
[self presentViewController:alert animated:YES completion:nil];
Thanks in advance.
#ion: Your answer led me to the right answer.
You indeed need to add one action with style UIAlertActionStyleCancel to have the menu button to work as expected and exit the UIAlertViewController.
To have this Cancel action hidden from the options (no button, like in the Settings app), just set its Title and Handler to nil:
[alert addAction:[UIAlertAction actionWithTitle:nil style:UIAlertActionStyleCancel handler:nil]];
You must have at least one UIAlertAction in the controller of style UIAlertActionStyleCancel for the menu button to work as you expect.
In Swift:
alertController.addAction(UIAlertAction(title: nil, style: .cancel, handler: nil))
So I'm working on an in app purchase in a SpriteKit game on AppleTV, but in the event of an error with payment or any other variety of errors, I want to display an error. UIAlertView is how I did it on iOS, but that's not an option on tvOS. Is there something similar that could be done instead? Basically, I need something to popup describing the error and a button to dismiss the popup. The ability to add more buttons (like UIAlertView) would be icing.
Note: I have researched this a bit and most things seem to point to using TVML, however, I don't believe that's an option mixed in with SpriteKit. I'll accept an answer related to this if it explains how to import something TVML (which I know next to nothing about) and run it alongside SpriteKit. I assume I'm looking for an answer unrelated to TVML though.
Check tvOS UIAlertController class :
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#"My Alert"
message:#"This is an alert."
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[alert addAction:defaultAction];
[self presentViewController:alert animated:YES completion:nil];
Edit : When using SpriteKit, last line is to replace with
UIViewController* controller = [UIApplication sharedApplication].keyWindow.rootViewController;
[controller presentViewController:alert animated:YES completion:nil];
Note that this class is also available in iOS since iOS 8 !
I'm trying to add something like this to my program but idk what it's called.
I'm talking about the buttons that pop up "Photo Library" "Take Photo or Video" "Cancel"
It's a UIAlertController with style UIAlertControllerStyleActionSheet.
You use it like this:
UIAlertController *aC = [UIAlertController alertControllerWithTitle:#"Title"
message:#"Message"
preferredStyle:UIAlertControllerStyleActionSheet];
[aC addAction:[UIAlertAction actionWithTitle:#"Button 1"
// Style can be default, destructive or cancel
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
// handler
}]];
// Add more actions (buttons) here if needed
// Assuming you're in your view controller,
// present the alert view controller like this:
[self presentViewController:aC animated:YES completion:nil];
UIActionSheet class from Cocoa touch.
I have this code sitting in a UIVIewController (XCode 6.1, iOS 8.1.1):
[UIAlertController showActionSheetInViewController:self
withTitle:#"Test Action Sheet"
message:NSLocalizedString(#"Are you sure you want to delete ALL appointments?",nil)
cancelButtonTitle:#"Cancel"
destructiveButtonTitle:#"Yes"
otherButtonTitles:#[#"No"] // same as Cancel
tapBlock:^(UIAlertController *controller, UIAlertAction *action, NSInteger buttonIndex){
if (buttonIndex == UIAlertControllerBlocksCancelButtonIndex) {
NSLog(#"Cancel Tapped");
} else if (buttonIndex == UIAlertControllerBlocksDestructiveButtonIndex) {
NSLog(#"Delete Tapped");
} else if (buttonIndex >= UIAlertControllerBlocksFirstOtherButtonIndex) {
NSLog(#"Other Action Index %ld", (long)buttonIndex - UIAlertControllerBlocksFirstOtherButtonIndex);
}
}];
When I run it, I get this run-time error:
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Your application has presented a UIAlertController (<UIAlertController: 0x7fdfe3324f00>) of style UIAlertControllerStyleActionSheet. The modalPresentationStyle of a UIAlertController with this style is UIModalPresentationPopover. You must provide location information for this popover through the alert controller's popoverPresentationController. You must provide either a sourceView and sourceRect or a barButtonItem. If this information is not known when you present the alert controller, you may provide it in the UIPopoverPresentationControllerDelegate method -prepareForPopoverPresentation.'
What do I need to do to make this work? (I have looked at SO and Google and found nothing specific). I appreciate any help I can get on this...
UPDATE
I re-wrote it without the 3rd-party code; added this code, and now it works!
UIAlertController * view= [UIAlertController
alertControllerWithTitle:#"My Title"
message:#"Select your Choice"
preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction* ok = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
//Do some thing here
[view dismissViewControllerAnimated:YES completion:nil];
}];
UIAlertAction* cancel = [UIAlertAction
actionWithTitle:#"Cancel"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[view dismissViewControllerAnimated:YES completion:nil];
}];
[view addAction:ok];
[view addAction:cancel];
view.popoverPresentationController.sourceView = self.view;
view.popoverPresentationController.sourceRect = CGRectMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0, 1.0, 1.0);
[self presentViewController: view animated:YES completion:nil];
The error message you received appeared because you ran iPhone code on an iPad. For use on an iPad, you have to set the alertController's popoverPresentationController. The source rectangle can be generated without the sloppy dimension calculations, too. Below, is a complete method, showing how you would encounter the code upon pressing a button. After setting up the AlertController the way you want, you get its popoverPresentationController and set it up for use with the iPad. In the method below, the button what was pressed is the sender. So we cast the sender back to that button, then use the button to set the rectangle. No messy dimensions need calculating. Now, if you run the code on the iPad, the popover will not display the Cancel button, (which does appear on the iPhone). That is by design. If you view the Apple UIPopoverController documentation, you see that the popover is cancelled by tapping outside of it.
- (IBAction)showImagePickerButtonTapped:(id)sender;
{
BOOL isCameraAvailable = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
BOOL isPhotoLibraryAvailable = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
[alertController addAction:[UIAlertAction actionWithTitle:#"Cancel" style:UIAlertActionStyleCancel handler:nil]];
if (isCameraAvailable) {
[alertController addAction:[UIAlertAction actionWithTitle:#"Camera" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self _showImagePickerWithSourceType:UIImagePickerControllerSourceTypeCamera];
}]];
}
if (isPhotoLibraryAvailable) {
[alertController addAction:[UIAlertAction actionWithTitle:#"Photo Library" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self _showImagePickerWithSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
}]];
}
// The following lines are needed for use with the iPad.
UIPopoverPresentationController *alertPopoverPresentationController = alertController.popoverPresentationController;
UIButton *imagePickerButton = (UIButton*)sender;
alertPopoverPresentationController.sourceRect = imagePickerButton.frame;
alertPopoverPresentationController.sourceView = self.view;
[self showDetailViewController:alertController sender:sender];
}
There's precious little information to go on here...
It appears you're using https://github.com/ryanmaxwell/UIAlertController-Blocks, not the standard UIAlertController, in which case the exception suggests changes that the version of the code you're using doesn't cover yet or a use case that requires extra work on your part.
I've never used this 3rd-party code but a quick check doesn't show any obvious "do this" in the docs. My initial recommendation would be to implement the delegate method on the view in question and give it what it wants - the location at which to present the popover.