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];
Related
I'm creating a wrapper for UIAlertView (I know about UIAlertController and about several already existing wrappers, it's also for educational purposes).
Suppose it looks like this (very shortened version):
#interface MYAlertView : NSObject
-(void)show;
#end
#interface MYAlertView()<UIAlertViewDelegate>
#end
#implementation MYAlertView
-(void)show {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Some title"
message:#"Some message"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:nil];
[alertView show]
}
#pragma mark UIAlertViewDelegate implementation
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
//Do something.
}
#end
And, for instance, I use it like this:
// USAGE (inside some ViewController)
-(void)showMyAlert {
dispatch_async(dispatch_get_main_queue(), ^{
MYAlertView *myAlertView = [[MYAlertView alloc] init];
[myAlertView show];
});
}
The problem I have is the following:
[myAlertView show] causes the alertView to appear. myAlertView is set as a delegate of the alertView.
There is the only strong reference to myAlertView: inside the block in the showMyAlert method. When it's finished, myAlertView is deallocated.
When the user clicks a button on the alertView, the alertView calls it's delegate method, but the delegate (myAlertView) is deallocated already, so it causes BAD_ACCESS (the delegate in UIAlertView is declared as assign, not weak).
I want to make MYAlertView as easy to use as it is with UIAlertView, so I don't want to make the user store a strong reference to it somewhere (it is inconvenient).
So, I have to keep the myAlertView alive as long as the alertView is shown somehow. The problem is I can't think of any way other than creating a strong reference inside MyAlertView, assigning it to self, when I show the alertView, and assigning it to nil, when I dismiss it.
Like so (only the changed bits):
#interface MYAlertView()<UIAlertViewDelegate>
//ADDED:
#property (nonatomic, strong) id strongSelfReference;
#end
#implementation MYAlertView
-(void)show {
UIAlertView *alertView = [[UIAlertView alloc] init /*shortened*/];
[alertView show]
//ADDED:
self.strongSelfReference = self;
}
#pragma mark UIAlertViewDelegate implementation
//ADDED:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
self.strongSelfReference = nil;
}
#end
It should work: the moment the alertView is dismissed, the strongSelfReference will be set to nil, there will be no strong references left to the myAlertView, and it will get deallocated (in theory).
But keeping a strong reference to self like this looks evil to me. Is there a better way?
UPDATE: The MYAlertView in reality is an abstraction layer around the now deprecated UIAlertView and a new UIAlertController (iOS 8+), so subclassing UIAlertView is not an option.
Yes, your object should keep a strong reference to itself. It's not evil to do so.
A self-reference (or, in general, any reference cycle) is not inherently evil. The evil comes in creating one unintentionally such that it is never broken, and thus leaks objects. You're not doing that.
I feel like the answer here is to actually implement MYAlertView as a subclass of UIAlertView instead of an object that floats in the ether. It will stick around as long as your internal UIAlertView would have regularly stuck around.
#interface MYAlertView : UIAlertView
#end
#implementation MYAlertView
- (instancetype)init {
if (self = [super initWithTitle:#"Some title"
message:#"Some message"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:nil]) {
// Other setup?
}
return self;
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
// Respond.
}
#end
Update: You should instead create an iOS7 analogy for UIAlertViewController.
#interface MyAlertViewController : UIViewController <UIAlertViewDelegate>
+ (id)makeMeOne;
#end
#implementation MyAlertViewController
- (void)viewDidAppear:(BOOL)animated {
UIAlertView *alert = [[UIAlertView alloc] init..];
[alert show];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
// Respond.
[self.navigationController popViewControllerAnimated:NO];
}
+ (id)makeMeOne {
if (iOS7) {
return [[self alloc] init];
} else {
return [[UIAlertViewController alloc] init];
}
}
#end
Fill in the blanks for setup.
In my opinion, this is an indicator of a bad design.
If you are creating a wrapper for both iOS version's you would be better off exposing a delegate for MYAlertView that mirrors the relevant dismiss actions (or else you won't be able to act on the callbacks and further than this wrapper).
If you are keeping a strong reference to yourself without adding any actual value to the class you are wrapping, maybe it would be better to write a wrapper that accepts a block to callback on completion? At least this way you can monitor the user's actions and let the caller dictate how long the alert is relevant.
After all, if you pass on the delegation then the problem is solved in a readable manner?
I have a UIAlertView from a reload button with 2 buttons - OK and Cancel. Cancel button works fine but when I want put some action (play again the game) in OK button doesn't work unless that action be a NSLog.
My code in m. file:
- (IBAction)startAgainAction:(id)sender {
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Warning" message:#"Have you short that want start again the game?"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:#"Cancel", nil];
[alert show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
// My OK button
if (buttonIndex == alertView.cancelButtonIndex) {
// Action to start the game again... (don't work)
[self viewDidLoad];
} else if (buttonIndex == alertView.firstOtherButtonIndex) {
// NSLog it's accept but not other actions...
NSLog(#"Cancel");
}
}
And yes, I have put UIAlertViewDelegate protocol in h. file
So, why viewDidLoad doesn't work when it calls the method again?
For reloading ... you should make a
- (void)reloadGame {}
method and reset everything manually. Something like:
- (void)reloadGame {
self.highScore = 0;
self.ballPosition = ...
// etc. depends on what you have
}
Also you could define some constants so you won't hardcode everything. and give them both in ViewDidLoad and reloadGame ... or better yet ... move all your code inside viewDidLoad into reloadGame and change it as so:
- (void)viewDidLoad {
[super viewDidLoad];
[self reloadGame];
}
Instead of having 2 .m files for the same class:
You should make your popOver class a different one and set it's delegate to your game class:
in your popOver class you should do:
#protocol CustomPopoverViewDelegate <NSObject>
- (void)doSomething;
// add other methods you need
#end
#interface CustomPopoverView : UIView
#property (nonatomic, retain) id <CustomPopoverView> delegate;
and when you open your popOver in your game class you should add:
//popover init/alloc
popover.delegate = self;
//show popover
also make sure your game class listens tot the popover delegate method
#import "CustomPopoverView.h"
#interface GameViewClass : UIViewController <CustomPopoverViewDelegate>
and in your customPopover class in a method you want to forward to your gameclass you just put
- (void)methodNameForDoSomething {
if ([self.delegate respondsToSelector:#selector(doSomething)]) { //always nice to check. Notice this is the same doSomething we declared in .h in the protocol
[self.delegate doSomething];
}
}
and the gameClass you will put
- (void)doSomething {
//whatever
}
you can also send parameters
You could also subclass ... (of course popover be another class with it's own .h)
and declare it as a subclass (you can do this when creating a new class and enter the class name you want to subclass as seen in the pic below)
and the your popover view's header will be like :
#interface CustomPopoverView : GameView
and will have all of GameView's methods and propertyes available.
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.
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)
}
}
}
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]);
}