How to properly use a UIAlertAction handler in Objective-C - objective-c

Simply put, I would like to call a method or reference a variable from my class in a UIAlertAction handler.
Do I need to pass in a block to this handler or is there some other means to achieve this?
#interface OSAlertAllView ()
#property (nonatomic, strong) NSString *aString;
#end
#implementation OSAlertAllView
+ (void)alertWithTitle:(NSString *)title message:(NSString *)msg cancelTitle:(NSString *)cancel inView:(UIViewController *)view
{
__weak __typeof(self) weakSelf = self;
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:msg
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:cancel style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
// I'd like to reference a variable or method here
weakSelf.aString = #"Apples"; // Not sure if this would be necessary
self.aString = #"Apples"; // Member reference type 'struct objc_class *' is a pointer Error
[self someMethod]; // No known class method Error
}];
[alert addAction:defaultAction];
[view presentViewController:alert animated:YES completion:nil];
}
- (void)someMethod {
}

The + at the start of your alertWithTitle... method means it is a class method. When you call it, self will be the class OSAlertAllView, not an instance of type OSAlertAllView.
There are two ways you could change it to make it work.
Change the + at the start of the method to a - making it an instance method. Then you would call it on an instance instead of the class.
// Old Class Method Way
[OSAlertAllView alertWithTitle:#"Title" message:#"Message" cancelTitle:#"Cancel" inView:viewController];
// New Instance Methods Way
OSAlertAllView *alert = [OSAlertAllView new];
[alert alertWithTitle:#"Title" message:#"Message" cancelTitle:#"Cancel" inView:viewController];
The other way would be to create an instance to OSAlertAllView inside of you alertWithTitle... and replace the uses of self with that object.
+ (void)alertWithTitle:(NSString *)title message:(NSString *)msg cancelTitle:(NSString *)cancel inView:(UIViewController *)view
{
OSAlertAllView *alertAllView = [OSAlertAllView new];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:msg
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:cancel style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
alertAllView.aString = #"Apples";
[alertAllView someMethod];
}];
[alert addAction:defaultAction];
[view presentViewController:alert animated:YES completion:nil];
}

Related

after calling an func for init the UIAlertController remains nil

I have 2 alerts I want to present in different cases, I wrote a general function to init the alerts in the beginning and change the messages later, but when I am trying to present the alert I get a crash. When I inspect the notesAlert in runtime it is still nil.
Can someone explain what I did wrong?
#interface viewController (){
UIAlertController *tableAlert;
UIAlertController *notesAlert;
}
#end
#implementation viewController
- (void)viewDidLoad {
[super viewDidLoad];
[self initAlert:tableAlert];
[self initAlert:notesAlert];
}
// func to init the alerts
-(void)initAlert:(UIAlertController*)alert{
alert = [UIAlertController alertControllerWithTitle: #"" message: #"" preferredStyle:UIAlertControllerStyleActionSheet];
[alert setModalPresentationStyle:UIModalPresentationPopover];
[alert.popoverPresentationController setSourceView:self.view];
UIPopoverPresentationController *popover = [alert popoverPresentationController];
CGRect popoverFrame = CGRectMake(0,0, self.view.frame.size.width/2, self.view.frame.size.width/2);
popover.sourceRect = popoverFrame;
UIAlertAction *dismiss = [UIAlertAction actionWithTitle:#"Ok" style:UIAlertActionStyleDefault handler:nil];
[alert addAction:dismiss];
}
- (IBAction)showNotes:(id)sender {
// here the notesAlert is still nil
[notesAlert setTitle:#"oops"];
[notesAlert setMessage:#"you pressed the wrong one"];
[self presentViewController:notesAlert animated:YES completion:nil];
}
#end
[self initAlert: notesAlert]; doesn't create notesAlert. Instead, you could use notesAlert = [self initAlert];
Maybe something like this:
#interface ViewController () {
UIAlertController *tableAlert;
UIAlertController *notesAlert;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableAlert = [self initAlert];
self.notesAlert = [self initAlert];
}
// func to init the alerts
- (UIAlertController *) initAlert {
UIAlertController *alert = [UIAlertController alertControllerWithTitle: #"" message: #"" preferredStyle: UIAlertControllerStyleActionSheet];
[alert setModalPresentationStyle:UIModalPresentationPopover];
[alert.popoverPresentationController setSourceView: self.view];
UIPopoverPresentationController *popover = [alert popoverPresentationController];
CGRect popoverFrame = CGRectMake(0,0, self.view.frame.size.width/2, self.view.frame.size.width/2);
popover.sourceRect = popoverFrame;
UIAlertAction *dismiss = [UIAlertAction actionWithTitle: #"Ok" style: UIAlertActionStyleDefault handler:nil];
[alert addAction: dismiss];
return alert;
}

UIAlertController disappearing since iOS 13

I have the following function that pops up a UIAlert which allows the user to update their Haptic Feedback setting:
- (void)requestHapticSetting{
UIWindow *alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
alertWindow.rootViewController = [[UIViewController alloc] init];
alertWindow.windowLevel = UIWindowLevelAlert + 1;
[alertWindow makeKeyAndVisible];
if(isHapticOn){
hapticMessage = #"Haptic feedback is currently\nturned ON.\nPlease update preference.";
}
else {
hapticMessage = #"Haptic feedback is currently\nturned OFF.\nPlease update preference.";
}
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#"Haptic Setting"
message:hapticMessage
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* onAction = [UIAlertAction actionWithTitle:#"TURN ON" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self saveHapticSettingOn];
}];
UIAlertAction* offAction = [UIAlertAction actionWithTitle:#"TURN OFF" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self saveHapticSettingOff];
}];
[alert addAction:offAction];
[alert addAction:onAction];
[alertWindow.rootViewController presentViewController:alert animated:YES completion:nil];
}
I have used this for a couple of years and it works great.
However, since updating to iOS 13 and updating to the latest Xcode, my alert pops up for less than a second before closing.
What has changed that could be making this happen? Thanks in advance.
What seems to have changed is that on iOS 12 and previous versions your app would hold a strong reference to the window just by calling [alertWindow makeKeyAndVisible];, in iOS 13 it doesn't anymore.
What's happening is that the only strong reference to your alertWindow is in your requestHapticSetting func, and as soon as this func returns, the window is destroyed, thus removing your alert from the view.
This might be fixed just by adopting iOS 13 scenes, but I haven't tested that. What I can suggest, which won't properly work if you are using scenes, is holding your alert window in a strong variable somewhere in your code, and then using it to present the alert. I'd suggest doing so in a singleton or AppDelegate itself.
//AppDelegate.h
...
#property (strong) UIWindow *alertWindow;
....
//AppDelegate.m
...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
self.alertWindow = [[UIWindow alloc] init];
self.alertWindow.rootViewController = [[UIViewController alloc] init];
self.alertWindow.windowLevel = UIWindowLevelAlert + 1;
...
}
...
//Your class that's presenting the alert
#import "AppDelegate.h"
...
- (void)requestHapticSetting{
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication;
[appDelegate.alertWindow makeKeyAndVisible];
if(isHapticOn){
hapticMessage = #"Haptic feedback is currently\nturned ON.\nPlease update preference.";
} else {
hapticMessage = #"Haptic feedback is currently\nturned OFF.\nPlease update preference.";
}
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#"Haptic Setting"
message:hapticMessage
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* onAction = [UIAlertAction actionWithTitle:#"TURN ON" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self saveHapticSettingOn];
[appDelegate.alertWindow setHidden:YES];
}];
UIAlertAction* offAction = [UIAlertAction actionWithTitle:#"TURN OFF" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self saveHapticSettingOff];
[appDelegate.alertWindow setHidden:YES];
}];
[alert addAction:offAction];
[alert addAction:onAction];
[appDelegate.alertWindow.rootViewController presentViewController:alert animated:YES completion:nil];
}
For Swift code, check this answer.
I have created a helper class which support new UIWindowScene and iOS 13.X and swift 5.X. You guys can try
https://github.com/emraz/ERAlertController

UItextField - iOS multiple line textfield on AlertView

Here is my code
I am adding uitextfield to alertview , what I need is to get the data from the array to uitextfield with multiple lines and should be editable.
NSString *selected = [lineItemsArray objectAtIndex:indexPath.row];
UIAlertController *controller = [UIAlertController alertControllerWithTitle:#"Notice" message:#"Selected" preferredStyle:UIAlertControllerStyleAlert];
[controller addTextFieldWithConfigurationHandler:^(UITextField *linetextField)
{
linetextField.text=selected;
}];
UIAlertAction *alertAction = [UIAlertAction actionWithTitle:#"Okay"
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action) {
[self.navigationController popViewControllerAnimated:YES];
NSLog(#"Dismiss button tapped!");
}];
[controller addAction:alertAction];
[self presentViewController:controller animated:YES completion:nil];
PackageLineItemViewController *pLineVC = [[PackageLineItemViewController alloc]init];
pLineVC.view.backgroundColor = [UIColor whiteColor];
[self.navigationController pushViewController:pLineVC animated:YES];
[self.view endEditing:YES];
I tried the solution for your question.If you use alertViewController with textField you can't set multiple line.For label you can set multiple line but for textfield you can't set.
#import "ViewController.h"
#interface ViewController ()<UITextFieldDelegate>
{
NSArray *arr;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
arr = [NSArray arrayWithObjects:#"iOS",#"Android",#"Windows",nil];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)actionShowClickTextField:(id)sender
{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil
message:#"Enter your text"
preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.delegate = self;
textField.text = #"";
for(int i=0;i<[arr count];i++)
{
textField.text = [textField.text stringByAppendingString:[NSString stringWithFormat:#"%#,\n",[arr objectAtIndex:i]]];
}
NSLog(#"The textField text is - %#",textField.text);
}];
[self presentViewController:alertController animated:YES completion:nil];
UIAlertAction *actionAlert = [UIAlertAction actionWithTitle:#"Save"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
// access text from text field
NSString *text = ((UITextField *)[alertController.textFields objectAtIndex:0]).text;
NSLog(#"The text is- %#",text);
}];
actionAlert.enabled = YES;
[alertController addAction:actionAlert];
}
#end
The printed output results are
The textField text is - iOS, Android, Windows
UITextFIeld is specific to one line. you cannot have multi-line textfeild. IF you need multi line , you should go for UITextView. Again you cannot add UITextView to the alert controller. You need to build a custom alert view.

No known class method for selector presentViewController in UIAlertController

I know it is a very simple question and i am working on it since 3 hour but didn't able to make it working. I am trying to implement UIAlertController to show error message using Apple documentation, but i am getting error on this line that no known class method for selector presentViewController [self presentViewController:alert animated:YES completion:nil]; I searched and got many solutions but none is working here. AlertMessageViewController is my custom class, which is inherited from UIViewController.
AlertMessageViewController.h
#import <UIKit/UIKit.h>
#interface AlertMessageViewController : UIViewController
+(instancetype)showAlert: (NSString *) title withMessage: (NSString*) message preferredStyle:(UIAlertControllerStyle)preferredStyle;
#end
AlertMessageViewController.m
#import "AlertMessageViewController.h"
#import <UIKit/UIKit.h>
#interface AlertMessageViewController ()
#end
#implementation AlertMessageViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
+(instancetype)showAlert: (NSString *) title withMessage: (NSString*) message preferredStyle:(UIAlertControllerStyle)preferredStyle
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"title" message:#"alertMessage" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *ok =[UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){NSLog(#"ok action");}];
[alert addAction:ok];
[self presentViewController:alert animated:YES completion:nil];
}
#end
Why is your return type for showAlert: method instancetype? And you didn't return anything, it should be void.
EDIT: Also, your method shouldn't be Class method
this should work:
-(void)showAlert: (NSString *) title withMessage: (NSString*) message preferredStyle:(UIAlertControllerStyle)preferredStyle
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"title" message:#"alertMessage" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *ok =[UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){NSLog(#"ok action");}];
[alert addAction:ok];
[self presentViewController:alert animated:YES completion:nil];
}
UPDATE:
Okay try this:
+(UIAlertController*)alertWithTitle: (NSString *) title withMessage: (NSString*) message preferredStyle:(UIAlertControllerStyle)preferredStyle
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle: preferredStyle];
UIAlertAction *ok =[UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){NSLog(#"ok action");}];
[alert addAction:ok];
return alert;
}
}
Then when you want to show it, call it like this:
[self presentViewController:[AlertMessageViewController alertWithTitle:#"Title" withMessage:#"Message" preferredStyle:UIAlertControllerStyleAlert] animated:YES completion:NULL];

Objective C Blocks in an Array Passed To Subclass of UIAlertView

(A working solution, based on the responses, is provided at the end of this post.)
I thought this would be a tidy way to handle the callbacks that a particular alert view needs to address, so I don't have a single delegate method filtering all of the alert button presses. Here is the code:
#import "LSAlertView.h"
#implementation LSAlertView
- (id) initWithTitle:(NSString *)title
message:(NSString *)message
actionBlocks:(NSArray*)_actionBlocks
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ...
{
self = [super initWithTitle:title
message:message
delegate:self
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:otherButtonTitles,nil];
if (self) {
self.cancelButtonIndex = 0;
actionBlocks = [_actionBlocks retain];
[self show];
}
return self;
}
- (void) dealloc {
[actionBlocks release];
[super dealloc];
}
- (void) alertView:(UIAlertView *)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex
{
void (^action)(void) = [actionBlocks objectAtIndex:buttonIndex];
action();
}
#end
This works fine for two buttons set up like this:
- (void) restartSearches {
NSArray *actionBlocks = [NSArray arrayWithObjects:
^{NSLog(#"Cancel Button Selected");},
^{NSLog(#"Delete Button Selected");},
nil];
alertDeletingSearches = [[LSAlertView alloc]
initWithTitle:#"You Are About To Delete Your Current Searches"
message:#"Select Delete to Continue"
actionBlocks:actionBlocks
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Delete", nil];
[alertDeletingSearches release];
}
But as soon as I add some useful calls in one of the blocks, like this
- (void) restartSearches {
NSArray *actionBlocks = [NSArray arrayWithObjects:
^{NSLog(#"Cancel Button Selected");},
^{
[mapController.theMap removeAnnotations:mapController.theMap.annotations];
[dataInterface deleteDB];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"changeToFavorites"
object:nil];
NSLog(#"Delete Button Selected");
},
nil];
alertDeletingSearches = [[LSAlertView alloc]
initWithTitle:#"You Are About To Delete Your Current Searches"
message:#"Select Delete to Continue" actionBlocks:actionBlocks
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Delete", nil];
[alertDeletingSearches release];
}
it freezes, and I get a EXC_BAD_ACCESS error.
Am I doing something fundamentally wrong, or is there a minor error in my logic?
UPDATE
Handled the variadic problem problem using Firoze's suggestion below. (Follows the examples given at Numbergrinder)
- (id) initWithTitle:(NSString *)title message:(NSString *)message actionBlocks:(NSArray*)_actionBlocks cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... {
self = [super initWithTitle:title message:message delegate:self cancelButtonTitle:cancelButtonTitle otherButtonTitles:otherButtonTitles, nil];
if (self) {
va_list args;
va_start(args, otherButtonTitles);
NSString* buttonTitle;
while ((buttonTitle = va_arg(args, NSString *))) {
[super addButtonWithTitle:buttonTitle];
}
self.cancelButtonIndex = 0;
actionBlocks = [_actionBlocks retain];
[self show];
}
return self;
}
Here is the header file:
#interface LSAlertView : UIAlertView <UIAlertViewDelegate> {
NSArray *actionBlocks;
}
- (id) initWithTitle:(NSString *)title message:(NSString *)message actionBlocks:(NSArray*)_actionBlocks cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ...;
#end
So I see a couple of issues with this.
One is that you need to copy those blocks as you put them in the array. Those blocks are created on the stack. If you want to pass them to your alert view and you expect the alert view to hold onto them for later use, you need to copy them to the heap first.
So something like this should work:
NSArray *actionBlocks = [NSArray arrayWithObjects:
[[^{NSLog(#"Cancel Button Selected");} copy] autorelease],
[[^{
[mapController.theMap removeAnnotations:mapController.theMap.annotations];
[dataInterface deleteDB];
[[NSNotificationCenter defaultCenter] postNotificationName:#"changeToFavorites" object:nil];
NSLog(#"Delete Button Selected");
} copy] autorelease]
, nil];
Note the [^someBlock copy] around each block literal there. That should solve one issue.
The other issue, to which I don't know the answer, is that this is a variadic method (takes a variable number of arguments). I don't know of a way in a variadic method to turn around and call another variadic method (the UIAlertView initializer), unless you have a variation of the second method that takes a va_list. This is the same issue we have in C, inherited in Objective C as far as I understand it.
I think you haven't run into that yet because you haven't tried enough buttons for that.
EDIT
Thinking about this further, I guess you could get around the second issue by iterating through the varargs and then calling [self addButtonWithTitle:arg] for each of them.
You might find Lambda Alert useful:
LambdaAlert *alert = [[LambdaAlert alloc]
initWithTitle:#"Test Alert"
message:#"See if the thing works."];
[alert addButtonWithTitle:#"Foo" block:^{ NSLog(#"Foo"); }];
[alert addButtonWithTitle:#"Bar" block:^{ NSLog(#"Bar"); }];
[alert addButtonWithTitle:#"Cancel" block:NULL];
[alert show];
And:
LambdaSheet *sheet = [[LambdaSheet alloc] initWithTitle:#"Action Sheet"];
[sheet addButtonWithTitle:#"Miles" block:^{ NSLog(#"Trumpet"); }];
[sheet addButtonWithTitle:#"Trane" block:^{ NSLog(#"Saxophone"); }];
[sheet addDestructiveButtonWithTitle:#"Monk" block:^{ NSLog(#"Piano"); }];
[sheet addCancelButtonWithTitle:#"Back to the Head"];
[sheet showInView:window];
Static library, easy to include with your project using an Xcode workspace.