UIAlertController is crashing with this error message:
Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior (<UIAlertController: 0x7fb9107674d0>)
also with a warning is thrown trying to capture the textFields objectAtIndex.
Any ideas ?
Warning.. Capturing 'controller' strongly in this block is likely to lead to a retail cycle.
I also tried to create a #property (weak) reference the warning goes away but the app still crashes with this:
-(void)viewWillAppear:(BOOL)animated
{
// self.controller = [UIAlertController alloc];
UIAlertController* controller = [UIAlertController alertControllerWithTitle:#"Add Alergy To List" message:nil preferredStyle:(UIAlertControllerStyleAlert)];
[controller addTextFieldWithConfigurationHandler:^(UITextField * nametextField) {
_nameTextField.text = [controller.textFields objectAtIndex:0].text;
}];
UIAlertAction *save = [UIAlertAction actionWithTitle:#"Save Data" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
[self save:nil];
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:#"Cancel Action" style:(UIAlertActionStyleCancel) handler:nil];
[controller addAction:save];
[controller addAction:cancel];
[self presentViewController:controller animated:YES completion:nil];
}
__weak __typeof(UIAlertController) weakController = controller;
__weak __typeof(UITextField) weakTextField = _nameTextField;
[controller addTextFieldWithConfigurationHandler:^(UITextField *nametextField){
// nameTextField is the textField being returned in the block
// if you want to use _nameTextField you need to make that weak too
nameTextField.text = [weakController.textFields objectAtIndex:0].text;
// or
weakTextField.text = [weakController.textFields objectAtIndex:0].text;
}];
Add
__weak __typeof(UIAlertController) weakController = controller;
before your addTextFieldWithConfigurationhandler:.
Then you should replace
_nameTextField.text = [controller.textFields objectAtIndex:0].text;
with
nameTextField.text = [weakController.textFields objectAtIndex:0].text;
Without the _.
_ is for internal access of properties. Here you have to update the field given in parameter
The fact is that you are in a block and if you want to get text from controller fields, you have to make a weak reference to it. The weak controller will prevent increase retain count.
You are trying to load alertcontroller before the viewcontroller is allocated this warning is because you are adding UIAlertController directly inside viewWillAppear
This method is called before the view controller's view is about to be added to a view hierarchy
Just give some time for view to get added to the view hierarchy.to do that use dispatch_after or some other delay functionality.
Since you are getting textfield input inside a block you can't use strong declaration,So you have to declare __weak typeof(UIAlertController) *weakcontroller = controller; before entering the block.Check apple developer site here for more understanding.
Overall modified code is given bekow:
-(void)viewWillAppear:(BOOL)animated{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
UIAlertController* controller = [UIAlertController alertControllerWithTitle:#"Add Alergy To List" message:nil preferredStyle:(UIAlertControllerStyleAlert)];
__weak typeof(UIAlertController) *weakcontroller = controller;
[controller addTextFieldWithConfigurationHandler:^(UITextField * nametextField) {
nametextField.text = [weakcontroller.textFields objectAtIndex:0].text;
}];
UIAlertAction *save = [UIAlertAction actionWithTitle:#"Save Data" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
[self save:nil];
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:#"Cancel Action" style:(UIAlertActionStyleCancel) handler:nil];
[controller addAction:save];
[controller addAction:cancel];
[self presentViewController:controller animated:YES completion:nil];
});
}
It will work without any issues.
Related
I am trying to show UIAlertController with text field. When it launches keyfoard automatically shows up as textfield get the focus automatically. How can I show alert with textfield without keyboard (it should show up only when user clicks on textfield).
Here is my code
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#"Collect Input" message:#"input message"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#"Submit" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
//use alert.textFields[0].text
}];
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:#"Cancel" handler:^(UIAlertAction * action) {
//cancel action
}];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
// A block for configuring the text field prior to displaying the alert
//[textField resignFirstResponder];
}];
[alert addAction:defaultAction];
[alert addAction:cancelAction];
[self presentViewController:alert animated:NO completion:nil];
There are a few solutions.
You can get a reference to the text field in the addTextFieldWithConfigurationHandler callback and store it in a local variable. Then in the completion handler of presentViewController you can call resignFirstResponder on the text field. But this solution is far from ideal because the keyboard will appear and then immediately be dismissed.
Better yet is to set the text field's delegate and implement the shouldBeginEditing delegate method. Add an instance variable to act as a flag. The first time shouldBeginEditing is called, not the flag isn't set, set it, and return NO. Then each time after, check the flag and return YES.
Here's the implementation for option 2:
Indicate that your class conforms to the UITextFieldDelegate protocol in the .m file:
#interface YourClassHere () <UITextFieldDelegate>
#end
Add an instance variable for the flag:
BOOL showKeyboard = NO;
Update your alert setup code to set the text field's delegate:
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.delegate = self;
}];
Implement the text field delegate method:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
if (showKeyboard) {
return YES;
} else {
showKeyboard = YES;
return NO;
}
}
This prevents the initial display of the keyboard but allows it any time after that.
I have a class named ManagerClass.
Manager Class has a function showUIAlertController:
- (UIAlertController*)showUIAlertController:(NSString *)title message:(NSString *)message actions:(NSArray<UIAlertAction*>* )actions
This function should show alert controller with the parameters received.
So far so good...
Now i would like to take these actions and edit them somehow. Something like:
UIAlertAction *action = actions.firstObject;
UIAlertAction *actionCopyWithAdditionalAction = [UIAlertAction actionWithTitle:action.title style:action.style handler:^(UIAlertAction * _Nonnull action) {
[action "performTheAction"]; //perform the original action
[ManagerClass doSomething];
}];
"performTheAction" does not exist - it is just for you to understand what i am trying to achieve.
Does anyone has an idea how this task can be achieved ?
Did not find a way to do that while looking at Apple's UIAlertAction API
https://developer.apple.com/reference/uikit/uialertaction
Do you mean to perform a method provided by your code. Then use:
[self performSelector:#selector(aMethod:)];
or when sending an object with:
[self performSelector:#selector(aMethod:)
withObject:(id)object];
Note, the self here is referencing to the same class, it could be somewhere else as well.
Edit *
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#"My Alert"
message:#"This is an alert."
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
NSLog(#"42.");
}];
[alert addAction:defaultAction];
[self presentViewController:alert animated:YES completion:nil];
The console logs out 42. Put instead every action do you need.
Why you want to call a second alert which only displays the first alert and execute some of your code? You can do that in the first alert, too.
//Create the UIAlertController
UIAlertController *theAlertController = [UIAlertController alertControllerWithTitle:#"Your Title" message:#"Your Message" preferredStyle:UIAlertControllerStyleAlert];
//Add an UIAlertAction which the user can click at
[theAlertController addAction:[UIAlertAction actionWithTitle:#"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
//Execute your own code
//[self myOwnCode];
//Close the AlertController after your code
[self dismissViewControllerAnimated:YES completion:nil];
}]];
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:theAlertController animated:YES completion:nil];
});
Hope i understand you right.
You could pass a alert action model instead of a UIAlertAction.
so your method would look something like this:
- (UIAlertController*)showUIAlertController:(NSString *)title message:(NSString *)message actions:(NSArray<MyActionModel*>* )actions
where MyActionModel is a class with 3 properties
#interface MyActionModel: NSObject {
#property NSString * title;
#property UIAlertActionStyle * style;
#property ((void)^(UIAlertAction *)) action;
}
Then you can create your UIAlertActions when you need them and also add in your manager callbacks.
P.S. Sorry if my Objective-C is not quite right, I'm a bit rusty.
Here's the problem up front: I have a UIAlertController that has a textfield. I want to save the content of that textfield as an NSString when the user touches a "Confirm" button in the alert. When the Confirm action block is executed, however, the alert is nil (presumably already dismissed and deallocated at that point), and thus so is its textfield, meaning I cannot save the textfield's text.
I am using a series of UIAlertControllers to allow a user to create a passcode for my app, such that any time the app comes to the foreground, the user is prompted for the code before the app can be used.
I created a category of UIAlertController with several convenience methods that return preconfigured alerts that I need to use. Here's one of them:
+ (UIAlertController*)passcodeCreationAlertWithConfirmBehavior:(void(^)())confirmBlock andCancelBehavior:(void(^)())cancelBlock {
UIAlertController *passcodeCreationAlert = [UIAlertController alertControllerWithTitle:#"Enter a passcode"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
[passcodeCreationAlert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.keyboardType = UIKeyboardTypeNumberPad;
}];
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:#"Cancel"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {
if (cancelBlock) {
cancelBlock();
}
}];
UIAlertAction* confirmAction = [UIAlertAction actionWithTitle:#"Confirm"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (confirmBlock) {
confirmBlock();
}
}];
[passcodeCreationAlert addAction:cancelAction];
[passcodeCreationAlert addAction:confirmAction];
passcodeCreationAlert.preferredAction = confirmAction;
confirmAction.enabled = NO;
return passcodeCreationAlert;
}
This method returns a UIAlertController that allows the user to enter their desired passcode into a textfield. When I call this method in my view controller, I pass blocks as parameters which are used as the UIAlertAction handlers:
- (void)presentCreatePasscodeAlert {
UIAlertController *alert = [UIAlertController passcodeCreationAlertWithConfirmBehavior:^{
firstPasscode = alert.textFields[0].text;
[self presentConfirmPasscodeAlert];
} andCancelBehavior:^{
[self presentEnablePasscodeAlert];
}];
alert.textFields[0].delegate = self;
[self presentViewController:alert animated:YES completion:nil];
}
To reiterate the problem now that there is more context: When the action block is entered at the line:
firstPasscode = alert.textFields[0].text;
the alert is nil, and so is its textfield, meaning I cannot save the textfield's text.
In a separate project, however, I tried getting the same functionality without using the category and custom convenience methods, and that works as desired:
- (void) createPassword {
UIAlertController *createPasswordAlert = [UIAlertController alertControllerWithTitle:#"Enter a password"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
__weak ViewController *weakSelf = self;
[createPasswordAlert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.delegate = weakSelf;
textField.keyboardType = UIKeyboardTypeNumberPad;
}];
UIAlertAction* confirmAction = [UIAlertAction actionWithTitle:#"Confirm"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
self.password = createPasswordAlert.textFields[0].text;
[self confirmPassword];
}];
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:#"Cancel"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self promptPasswordCreation];
}];
[createPasswordAlert addAction:confirmAction];
[createPasswordAlert addAction:cancelAction];
confirmAction.enabled = NO;
[self presentViewController:createPasswordAlert animated:YES completion:nil];
}
Sure enough, in the above code the alert exists when the Confirm block is entered, and I can save the text just fine.
Have I done something screwy by passing blocks as parameters to my convenience methods?
One of your problems is that when you create the block and send it into the initializer you are referencing a nil object inside the block. Then the block is saved with that nil reference and passed as a block to your handler.
UIAlertController *alert = [UIAlertController passcodeCreationAlertWithConfirmBehavior:^{
firstPasscode = alert.textFields[0].text;
[self presentConfirmPasscodeAlert];
} andCancelBehavior:^{
[self presentEnablePasscodeAlert];
}];
In there alert is not initialized yet when you create that block and send it to be saved. An option to fix that would be to use a standard initializer and then categorize a method to add the block functions that you want. Another option might be to try to add the __block modifier to the alert object, although I don't think that will help.
Inside of a forin loop, I need to present an UIAlertController and wait for user confirmation before presenting the next one. I presented them inside a forin loop, but only the first one appears(after confirmation, the others don't show up).Any help would be greatly appreciated.
You can use the UIAlertController delegate when a button is pressed you show the next alert.
Make a global alert index:
NSUInteger alertIndex = 0;
Make a global NSArray with your alert details in a NSDictionary eg:
self.alerts = #[#{#"title":#"Alert", #"message":#"Message 1"}, #{#"title":#"Alert", #"message":#"Message 2"}];
Call your first alert with an index, eg:
...title:self.alerts[alertIndex][#"title"]...
and
...message:self.alerts[alertIndex][#"message"]...
In your alert controller delegate's didClickButtonAtIndex:
alertIndex++;
// call your next alert here, explained above.
I need to do something similar to display hints to users throughout my app.
Just managed to cobble this together and it works quite well and is easy to adapt and add to ...
/////
// Alert Properties in #interface
////
#property NSOperationQueue *queue;
#property NSMutableArray* operationArray;
#property int operationCounter;
#property int operationTotal;
/////
// Alert Properties End
////
/////////
// Multi Alert Code Start
////////
- (void) alertCodeWithTitleString: (NSString*) titlestring AndMessageString:(NSString*)messagestring {
NSOperation *tempoperation = [NSBlockOperation blockOperationWithBlock: ^(void) {
NSString* tutTitleString = titlestring;
NSString* tutMessageString = messagestring;
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController * alert= [UIAlertController
alertControllerWithTitle:tutTitleString
message:tutMessageString
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* dontshow = [UIAlertAction
actionWithTitle:#"Don't Show This Again"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[alert dismissViewControllerAnimated:YES completion:nil];
self.operationCounter++;
[self alertOperationCallMethod2];
}];
UIAlertAction* done = [UIAlertAction
actionWithTitle:#"Close"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[alert dismissViewControllerAnimated:YES completion:nil];
self.operationCounter++;
[self alertOperationCallMethod2];
}];
[alert addAction:done];
[alert addAction:dontshow];
[self presentViewController:alert animated:YES completion:nil];
});
} ];
self.operationTotal++;
[self.operationArray addObject:tempoperation];
}
-(void) alertOperationCallMethod1 {
self.operationCounter = 0;
self.operationTotal = 0;
self.queue = [[NSOperationQueue alloc] init];
self.operationArray = [[NSMutableArray alloc] init];
[self alertCodeWithTitleString:#"Title1" AndMessageString:#"Message1"];
[self alertCodeWithTitleString:#"Title2" AndMessageString:#"Message2"];
[self alertCodeWithTitleString:#"Title3" AndMessageString:#"Message3"];
// Just keep adding method calls here to add alerts
[self alertOperationCallMethod2];
}
-(void) alertOperationCallMethod2 {
if (self.operationCounter<self.operationTotal) {
[self.queue addOperation:self.operationArray[self.operationCounter]];
}
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
//something here
[self alertOperationCallMethod1];
}
/////////
// Multi Alert Code End
////////
----
All you need to do is add another method call in alertOperationCallMethod1
Multiple versions of alertCodeWith..... method should allow you to customise your alerts to suit.
Hope this helps someone : )
This UIViewController extension does what you want:
The alerts queue up, fifo. I.e. latter alers wait and don't show until
user respond to former alerts.
I have an app that consists of several view controllers, in each view controllers I could load a UIAlertView.
What would be the best way of having a global alert view function? So I could effectively have a function bit like
[GlobalAlertVIew alertview : 2];
the 2 referring to what alert it is (bit like a enum).
I have tried adding an NSObject file and calling that, however on the return function
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
how do I get from this file in the NSObject to the original view controller?
Thanks
You either need to configure the delegate on the configured UIAlertView
UIAlertView *alertView = [GlobalAlertView alertView:2];
alertView.delegate = self;
or expose the delegate in the factory method
[GlobalAlertView alertView:2 withDelegate:self];
I don`t know if you still need, but I did like this:
+ (void)alert: (UIViewController *) view title: (NSString *) title message: (NSString *) message;
+ (void)alert: (UIViewController *) view title: (NSString *) title message: (NSString *) message {
UIAlertController * alert= [UIAlertController
alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[alert addAction:defaultAction];
[view presentViewController:alert animated:YES completion:nil];
}
You can add the following code to your constants header file and import this header at .pch file to be able to see this file at app ,
#define SHOW_ALERT(title,msg){ UIAlertController *noDataFoundAlert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action){}]; [noDataFoundAlert addAction:okAction]; [self presentViewController:noDataFoundAlert animated:YES completion:nil];}
And you can call this method at any class like the following call ,
SHOW_ALERT(#"Please select any section", #"Please select any section");
Nada Gamal