Custom UIAlertView with block callback - ios

MyAlertView (subclass of UIAlertView) has this method:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (self.clickedButtonAtIndexBlock != NULL)
self.clickedButtonAtIndexBlock(buttonIndex);
}
My question is how do I define the callback when I create the alert view? Obviously this is wrong:
alert.clickedButtonAtIndexBlock = ^{
NSLog(#"clicked: %d", buttonIndex);
}

I have written a blog post about how to (and why) add block callbacks to alert views, action sheets and animations:
http://blog.innovattic.com/uikitblocks/
If you just want a working implementation of this you can download the sources files from GitHub:
https://github.com/Innovattic/UIKit-Blocks
Usage:
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"My easy alert"
message:#"Would you like to perform some kind of action?"
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
[alert setHandler:^(UIAlertView* alert, NSInteger buttonIndex) {
NSLog(#"Perform some kind of action");
} forButtonAtIndex:[alert firstOtherButtonIndex]];
[alert show];

Try doing something like this (I haven't tested it):
.h
typedef void (^MyClickedIndexBlock)(NSInteger index);
#interface YouInterface : YourSuperclass
#property (nonatomic, strong) MyClickedIndexBlock clickedIndexBlock;
#end
.m
//where you have to call the block
if (self.clickedIndexBlock != nil)
self.clickedIndexBlock(buttonIndex);
// where you want to receive the callback
alert.clickedIndexBlock = ^(NSInteger index){
NSLog(#"%d", index);
};

Check this OpinionzAlertView I have used it in few projects, works good for me. Here is sample:
OpinionzAlertView *alert = [[OpinionzAlertView alloc] initWithTitle:#"title"
message:#"message"
cancelButtonTitle:#"No, thanks"
otherButtonTitles:#[#"Done"]
usingBlockWhenTapButton:^(OpinionzAlertView *alertView, NSInteger buttonIndex) {
NSLog(#"buttonIndex: %li", (long)buttonIndex);
NSLog(#"buttonTitle: %#", [alertView buttonTitleAtIndex:buttonIndex]);
}];
[alert show];
I hope it will be helpful to you.

I have written a simple class LMSVBlocks to easily display alerts and get block callbacks in 1 line. Hopefully you will find it useful for this purpose
https://github.com/sourabhverma/LMSVBlocks
Concept: To make UIAlertView block compatible you need another class (Say LMSVBlockAlert) to handle the delegate method and when UIAlertView delegate will give a callback, LMSVBlockAlert class can send a call back in the block.
Code:
(LMSVBlockAlert.m)
Maintain all the instances of LMSVBlockAlert in an array so that they have a strong reference
static NSMutableArray *_LMSVblockHandlersArray = nil;
Keep the block handler in LMSVBlockAlert
#interface LMSVBlockAlert() <UIAlertViewDelegate>
#property (nonatomic, copy) void (^cancelCompletionBlock)();
#property (nonatomic, copy) void (^confirmCompletionBlock)();
#end
When a new alert is fired create new instance of LMSVBlockAlert which has a UIAlertView and delegate callbacks
+(LMSVBlockAlert*)newInstance{
LMSVBlockAlert *newIns = [[LMSVBlockAlert alloc] init];
[LMSVBlockAlert updateHandlerArrayWith:newIns];
return newIns;
}
When alert delegate is fired in LMSVBlockAlert send callback to block and clear this from memory
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
switch (buttonIndex) {
case 0://Cancel
{
if(_cancelCompletionBlock){
_cancelCompletionBlock();
}
}
break;
case 1://OK
{
if(_confirmCompletionBlock){
_confirmCompletionBlock(alertView);
}
}
break;
default:
break;
}
[_LMSVblockHandlersArray removeObject:self];
}
Now you can have two simple method which can give you UIAlertView callbacks
+(UIAlertView*)promptAlertTwoBtn:(NSString*)msg title:(NSString*)title onCancel:(void (^)())onCancel onConfirm:(void (^)())onConfirm{
return [[LMSVBlockAlert newInstance] showAlertMainWithTitle:title msg:msg onCancel:^{
onCancel();
} onConfirm:^(UIAlertView *alertView) {
onConfirm();
}];
}
-(UIAlertView*)showAlertMainWithTitle:(NSString*)title msg:(NSString*)msg onCancel:(void (^)())onCancel onConfirm:(void (^)(UIAlertView*))onConfirm{
UIAlertView *newAlert = nil;
newAlert = [[UIAlertView alloc]
initWithTitle:title
message:msg
delegate:self
#"Cancel"
otherButtonTitles:#"Confirm", nil];
[newAlert show];
self.cancelCompletionBlock = onCancel;
self.confirmCompletionBlock = onConfirm;
return newAlert;
}
Finally
Hope you found it useful..

You Can simple use these category classes from github.
Alert_ActionSheetWithBlocks
This provide Dismiss blocks for both AlertView and action sheets.
For Eg.
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"AlertView+Block" message:#"WithBlocks" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"newAlertViewWithTextFields",#"newAlertViewWithSingleTextField", nil];
[alert showWithFinishBlock:^(UIAlertView *alertView, NSInteger buttonIndex)
{ if (buttonIndex == 0) { } else if (buttonIndex == 1) { } }];
Apart from this it is also providing methods for text fields..
-(void) showWithFinishBlock:(FinishBlock_)block_; //-- AlertView with TextField [simple or secure]
-(void) showWithTextFieldBlock:(TextFieldBlock_)block_ secure:(BOOL)isSecure; //-- AlertView with two textfields username & password
You can take a look at example bundled with it.
I hope it will be helpfull to you.

Try out this
Say you created a class called MyCustomAlert, and you declare it as this variable.
MyCustomAlert *myCustomAlert = [[MyCustomAlent alloc] init];
You would put this in your header file.
- (void)setCompletion:(void (^)(int selectedButtonIndex))completion;
And you would put this in your implementation file
typedef void (^IntBlock)(int intBlock);
IntBlock _completion;
- (void)setCompletion:(void (^)(int selectedButtonIndex))completion{
_completion = completion;
}
now in the project where you declared "myCustomAlert". If you type in
[myCustomAlert setCompletion: // And select the autocomplete item
you will end up with this
[myCustomAlert setCompletion:<#^(int intBlock)completion#>]
the value <#^(int intBlock)completion#> will show up as a bubble like this.
when you press enter on the value, it will fill in the Block for you to use.
[myCustomAlert setCompletion:^(int selectedButtonIndex) {
}
when you want to trigger the _completion block in your custom class, you would call it somewhere in your code as follows.
- (void) callCompletionWithButtonIndex:(int) index{
if (_completion != nil) _completion(index);
}
hope that clears up the complication.

I have written a simple extension in Swift, hope it helpful
import UIKit
extension UIAlertView {
func show(completion: (alertView: UIAlertView, buttonIndex: Int) -> Void){
self.delegate = AlertViewDelegate(completion: completion)
self.show()
}
class func showInput(title: String?, message: String?, cancellable: Bool, completion: (text: String?) -> Void){
var strOK = NSLocalizedString("OK",comment: "OK")
var strCancel = NSLocalizedString("Cancel",comment: "Cancel")
var alert = UIAlertView(title: title, message: message, delegate: nil, cancelButtonTitle: cancellable ? strCancel : strOK)
alert.alertViewStyle = UIAlertViewStyle.PlainTextInput
if(cancellable) {
alert.addButtonWithTitle(strOK)
}
alert.show { (alertView, buttonIndex) -> Void in
if(cancellable && alertView.cancelButtonIndex == buttonIndex) {
completion(text: nil)
return
}
completion(text: alertView.textFieldAtIndex(0)?.text)
}
}
private class AlertViewDelegate : NSObject, UIAlertViewDelegate {
var completion : (alertView: UIAlertView, buttonIndex: Int) -> Void
var retainedSelf : NSObject?
init(completion: (UIAlertView, Int) -> Void ) {
self.completion = completion
super.init()
self.retainedSelf = self
}
func alertView(alertView: UIAlertView, didDismissWithButtonIndex buttonIndex: Int) {
var retain = self
retain.retainedSelf = nil
retain.completion(alertView: alertView, buttonIndex: buttonIndex)
}
}
}

Related

How to run a selector when a button is pressed in UiAlertView

So I have the following code and when the round is over it pops up with the following UIAlertView. Now when the user pushes the leaderboards button I want it to run the - (void) statement I have that displays the leaderboards. Please not that this is all inside of a sprite kit view controller. How should I do this?
Also I have my void statement in a different class, is there anyway to bring that over via an #import?
- (void) gameEnded
{
// indicate our game state as stopped
_gameState = STOPPED;
// create a message to let the user know their score
NSString *message = [NSString stringWithFormat:#"You scored %d this time", _score];
// show the message to the user
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Game over!"
message:message
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:#"Leaderboards",nil];
[av show];
// reset the score tracker for the next game
_score = 0;
//reset playing area
[self removeAllBlocks];
[self addBlocks];
}
Here:
// show the message to the user
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Game over!"
message:message
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:#"Leaderboards",nil];
You need to set the delegate to 'self', or another class that you want be handling the alert view events as its delegate. That class must conform to the UIAlertViewDelegate, for example:
#interface YourController : YourControllerSuperClass <UIAlertViewDelegate>
Then, Xcode will probably suggest you to implement:
– alertView:clickedButtonAtIndex:
As this is the method called by the alert view on its delegate when a button is pressed.
Here you'll receive a reference to your alert view and the index of the pressed button.
With this, you can find which action was performed.
For example:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
// In case there are more then one alert view and a reference to each of the alert views are kept.
if(alertView == self.avAlertView){
switch(buttonIndex){
// Your button's cases!
}
}
}
Your current Controller should implement UIAlertViewDelegate, and call the method alertView:clickedButtonAtIndex: with the index of your leaderboard button.
Your CustomController.h:
#import <UIKit/UIKit.h>
#interface AdsEventViewController : UITabBarController<UIAlertViewDelegate>
#end
Your CustomController.m:
[...]
- (void) gameEnded
{
// indicate our game state as stopped
_gameState = STOPPED;
// create a message to let the user know their score
NSString *message = [NSString stringWithFormat:#"You scored %d this time", _score];
// show the message to the user
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Game over!"
message:message
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:#"Leaderboards",nil];
[av show];
// reset the score tracker for the next game
_score = 0;
//reset playing area
[self removeAllBlocks];
[self addBlocks];
}
[...]
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if(buttonIndex == _LEADERBOARD_BUTTON_INDEX_ ){
// leaderboard method
}
}

How to properly subclassing a UIActionSheet

I want subclass a UIActionSheet to use a block approach instead of delegate.
My problem is when I call the super initialization on UIActionSheet the variadic ... at the end of the method aren't recognized as a va_list and the action sheet only show the first button.
Here class implementation .m
#interface FLActionSheet ()
#property (nonatomic,strong) actionClickedBlock clickedBlock;
#end
#implementation FLActionSheet
+ (id)actionSheetWithTitle:(NSString *)title
clickedBlock:(actionClickedBlock)clickedBlock
cancelButtonTitle:(NSString *)cancelButtonTitle
destructiveButtonTitle:(NSString *)destructiveButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ...
{
return [[self alloc] initWithTitle:title
clickedBlock:clickedBlock
cancelButtonTitle:cancelButtonTitle
destructiveButtonTitle:destructiveButtonTitle
otherButtonTitles:otherButtonTitles];
}
- (id)initWithTitle:(NSString *)title
clickedBlock:(actionClickedBlock)clickedBlock
cancelButtonTitle:(NSString *)cancelButtonTitle
destructiveButtonTitle:(NSString *)destructiveButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ...
{
self = [super initWithTitle:title
delegate:self
cancelButtonTitle:cancelButtonTitle
destructiveButtonTitle:destructiveButtonTitle
otherButtonTitles:otherButtonTitles,nil];
if (self)
{
self.clickedBlock = [clickedBlock copy];
}
return self;
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
self.clickedBlock(buttonIndex);
}
#end
And here how I initialize the action sheet:
[[[FLActionSheet alloc] initWithTitle:#"Ordina per..."
clickedBlock:^(NSInteger buttonIndex) {
switch (buttonIndex)
{
case 0:
break;
default:
break;
}
}
cancelButtonTitle:nil
destructiveButtonTitle:#"Annulla"
otherButtonTitles:#"Data crescente", #"Data decrescente", #"Mittente crescente", #"Mittente decrescente"]
showFromBarButtonItem:myBarButtonItem
animated:YES];
And here the result:
I'm definitely doing something wrong but I do not understand what.
Ideas ?
UIActionSheet is not designed to be subclassed.
There are other ways how to use it with blocks. Best is to create a category, that implements delegate protocol and store the block using associated ojects mechanism. Implementation here.
I think initWithTitle:delegate:cancelButtonTitle:... is not the designated initializer and it is implemented using [self init] with following setTitle: and addButtonWithTitle: calls. You should do it similarily.
In fact, with variadic method, you have no other option. To collect all arguments, you have to use va_list and related functions. Implementation here. Then on each of them you call addButtonWithTitle: as I mentioned.
Please pass nil at the last of your otherButtonTitles parameter.
[[FLActionSheet alloc] initWithTitle:#"Ordina per..."
clickedBlock:^(NSInteger buttonIndex) {
switch (buttonIndex)
{
case 0:
break;
default:
break;
}
}
cancelButtonTitle:nil
destructiveButtonTitle:#"Annulla"
otherButtonTitles:#"Data crescente", #"Data decrescente", #"Mittente crescente", #"Mittente decrescente", nil]
showFromBarButtonItem:myBarButtonItem
animated:YES];

Which button on my UIAlertView did the user press?

I've created a UIAlertView and now I want to check which button the user presses.
My code is:
- (IBAction)button1 {
{
UIAlertView *alert1 = [[UIAlertView alloc] init];
[alert1 setTitle:#"Hello"];
[alert1 setMessage:#"Do you like smoking?"];
[alert1 addButtonWithTitle:#"Yes"];
[alert1 addButtonWithTitle:#"No"];
[alert1 show];
}
}
How can I check it with if-else statement?
You must set the delegate of your UIAlertView to the class that will handle the callbacks from the UIAlertView itself
E.g.
[alert1 setDelegate:self];
Where self is your current UIViewController that implements the protocol <UIAlertViewDelegate>.
When the user taps a button, the UIAlertView will call back to whatever object you set as the delegate, in my example we are using the UIViewController that created the UIAlertView. Once we get the callback, we can check which button index has been tapped and act accordingly. This is a basic delegation pattern that is used throughout iOS development, especially UIKit.
Example
#interface MyViewController : UIViewController() <UIAlertViewDelegate>
#end
#implementation MyViewController
- (IBAction)button1 {
{
UIAlertView *alert1 = [[UIAlertView alloc] init];
[alert1 setTitle:#"Hello"];
[alert1 setMessage:#"Do you like smoking?"];
[alert1 addButtonWithTitle:#"Yes"];
[alert1 addButtonWithTitle:#"No"];
[alert1 setDelegate:self];
[alert1 show];
}
}
#pragma mark - UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// Handle interaction
switch (buttonIndex)
{
case 0:
NSLog(#"Yes was pressed");
break;
case 1:
NSLog(#"No was pressed");
break;
}
}
#end
It is very important you specify in the header file, or class interface extension that your class implements the <UIAlertViewDelegate>.
I recommend you see the UIAlertViewDelegate Protocol Reference for more information, and you can follow this approach for many other UIKit components.
Implement UIAlertViewDelegate protocol in your view controller, set it as a delegate to the UIAlertView, and wait for the alertView:clickedButtonAtIndex: event, like this:
#interface MyViewController : UIViewController <UIAlertViewDelegate>
-(void)alertView:(UIAlertView *)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex;
#end
#implementation MyViewController
-(void)alertView:(UIAlertView *)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex {
NSLog(#"Button %d clicked...", (int)buttonIndex);
}
#end
Change the code that displays the alert view as follows:
UIAlertView *alert1 = [[UIAlertView alloc] init];
[alert1 setTitle:#"Hello"];
[alert1 setMessage:#"Do you like smoking?"];
[alert1 addButtonWithTitle:#"Yes"];
[alert1 addButtonWithTitle:#"No"];
alert1.delegate = self; // <<== Add this line
[alert1 show];
Adding on to the answer by Jeff, I think that you have to enable the other button in order to place your logic when the button is clicked.
In order to creating the button with your code:
- (IBAction)button1
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Hello"
message:#"Do you like smoking?"
delegate:self
cancelButtonTitle:#"Yes"
otherButtonTitles:#"No", nil];
[alert show];
}
But before knowing which button is clicked, you have to enable the first other button, by calling this delegate method:
- (BOOL)alertViewShouldEnableFirstOtherButton:(UIAlertView *)alertView
{
return YES;
}
This will enable the NO button that you have created. Then, you will be able to do some logic with the clickedButtonAtIndex method, which I suppose you will be doing.
Implement the UIAlertView delegate method:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0) {
// i love smoking!
} else if (buttonIndex == 1) {
// i hate smoking!
}
}
Be sure to declare the UIAlertViewDelegate in your header class.
Make sure that the alertViewShouldEnableFirstOtherButton: method returns YES, if not you will not be able to put in your logic for when the button is pressed.
Hope this helps! :)
The best thing you can do is make the class that presents your alert view conform to the UIAlertViewDelegate protocol and implement the method –alertView:clickedButtonAtIndex:. That way you'll know what button was clicked.
Hope this helps!

Delegate and Protocol code not entering delegate method

I am working on an user activation errors, I have a NSObject class that gets call if an error is returned from the DB.
I show an alertview that has a method called when the user presses the UIButton on the alert view. This is what the method looks like.
//ErrorHandling.m
//
case 1: {
NSLog(#"ERROR = 1");
message = [[UIAlertView alloc] initWithTitle:#"Error 1:"
message:#"The activation request failed."
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
message.tag = myAlertViewsTag;
[self performSelector:#selector(showAlertViewAndMessage) withObject:message afterDelay:0.3]; // set timer to give any huds time to load so I can unload them correctly
}
break;
//
- (void)showAlertViewAndMessage {
[SVProgressHUD dismiss];
[message show];
}
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (alertView.tag == myAlertViewsTag) {
if (buttonIndex == 0) {
if (receivedResponseData != nil) {
if (errorCodeValue == 1) {
[[self errorDataDelegate] passErrorDataToRoot:receivedResponseData];
}
// incase the user is further on in the navigationstack bring them back to the rootview
[self.currentNavigationController popToRootViewControllerAnimated:YES];
}
}
}
so far all this code works, accept for the delegate/protocol request... I have checked and double checked my code, however I think maybe I am missing something that maybe you can see. This is what my Delegate and Protocol looks like.
//errorHandling.h
#protocol RecivedErrorData <NSObject>
- (void)passErrorDataToRoot:(NSData *)errorData;
#end
//Protocol/delegate
__weak id <RecivedErrorData> errorDataDelegate;
//Protocol/delegate
#property (weak, nonatomic) id <RecivedErrorData> errorDataDelegate;
//errorHandling.m
//delegate / protocols
#synthesize errorDataDelegate;
[[self errorDataDelegate] passErrorDataToRoot:receivedResponseData];
//RootViewController.h
#import "ErrorHandling.h"
#interface RootViewController : UIViewController <RecivedErrorData> {
// error handling for activations
ErrorHandling *errorHandling;
//RootViewController.m
-(void)viewDidLoad {
errorHandling = [[ErrorHandling alloc] init];
[errorHandling setErrorDataDelegate:self];
}
#pragma ErrorProtocol
- (void)passErrorDataToRoot:(NSData *)errorData {
NSLog(#"WORKED");
}
So thats my code for the protocol and delegate, it almost works when the button is clicked it just never maked it to passErrorDataToRoot delegate method.
I am wondering if its an error in initialization, ErrorHandling.h is initialized originally when the app starts up inside the rootView, then when I get an error from a request I call ErrorHandling.m from a class called EngineRequest.m using alloc init etc... that's the only thing I can think of, that because of this extra allocation im dealing with another method but I am not sure this is the reason? I thought delegates and protocols were used to avoid this issue of reallocation.

Send a parameter to an AlertView

I have a delegate, which recives a message to delete a item with that item as an argument.
I want to show a confirmation AlertView, and then, if the users press Yes, i want to delete it.
So, what I have is
The delegate method that gets called:
- (void) deleteRecording:aRecording(Recording*)aRecording {
NSLog(#"Cancel recording extended view");
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle: NSLocalizedString(#"Cancel recording",nil)
message: NSLocalizedString(#"Are you sure you want to cancel the recording?",nil)
delegate: self
cancelButtonTitle: NSLocalizedString(#"No",nil)
otherButtonTitles: NSLocalizedString(#"Yes",nil), nil];
[alert show];
[alert release];
}
And the method thats checks which button has been pressed:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
switch (buttonIndex) {
case 0:
{
NSLog(#"Delete was cancelled by the user");
}
break;
case 1:
{
NSLog(#"Delete deleted by user");
}
}
}
So, my question is, how can i send the aRecording parameter from the first method to the second?
Thanks a lot
Store that variable in a member variable (easiest solution)
If you are only passing an int variable, you can set AlertView tag
property.
myAlertView.tag = YOUR_INT;
According to the documentation,
Note : The UIAlertView class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is
private
and must not be modified.
So please use the 3rd method only if you are not intending to submit
app to app store. Thanks user soemarko ridwan for the tip.
For passing complex objects, subclass UIAlertView, add an object
property
#interface CustomAlertView : UIAlertView
#property (nonatomic, retain) id object;
#end
#implementation CustomAlertView
#synthesize object;
- (void)dealloc {
[object release];
[super dealloc];
}
#end
When you create AlertView
CustomAlertView *alert = [[CustomAlertView alloc]
initWithTitle: NSLocalizedString(#"Cancel recording",nil)
message: NSLocalizedString(#"Are you sure you want to cancel the recording?",nil)
delegate: self
cancelButtonTitle: NSLocalizedString(#"No",nil)
otherButtonTitles: NSLocalizedString(#"Yes",nil), nil];
[alert setObject:YOUR_OBJECT];
[alert show];
[alert release];
In the delegate
- (void)alertView:(TDAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
NSLog(#"%#", [alertView object]);
}

Resources