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?
Related
I am working on XCode7 and on this project I am working on, UIAlertview is working fine.
I set up a cocoa-touch file, defined a new class inheriting from NSobject,
NSVerifier.h
#interface NSVerifier : NSObject<UITextFieldDelegate, UIAlertViewDelegate>
{
..
}
and inside the implementation I have:
NSVerifier.m
- (void)alertView:(UIAlertView *)aView clickedButtonAtIndex:(NSInteger)buttonIndex{
//Breakpoint here
..
}
-(void)show{
UIAlertView* t = [[UIAlertView alloc] initWithTitle:#"test" message:#"testing" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"ok", nil];
[t show];
}
inside viewDidAppear:
NSSVerifier* iVerifier = [[NSVerifier alloc] init];
[iVerifier show];
I was trying all day to make that breakpoint stop when I press the ok button, but the breakpoint was never reached.
On the other hand...
I have another project that was made during iOS7 times, and I imported this class in. It works there...
When I put the delegation methods I got a warning the UIAlertView is deprecated in iOS9, and on which device I am debugging (iOS9), But since the deployment target on both devices is iOS7, should this not work still?
Have you set the alert delegate to be self:
t.delegate = self;
Try that?
Update: And make sure where your NSVerifier is created it has a strong reference so the UIAlertView delegate doesn't get dropped later once the UIAlertView is shown. E.g in your ViewController create iVerifier in your .h:
#property (nonatomic, strong) NSVerifier *iVerifier;
Just another thought
If you don't retain iVerifier and it gets deallocated causing UIAlertView's delegate property to be nil.
Following should help:
// In interface
#property (strong, nonatomic) NSSVerifier* iVerifier;
// In implementation
NSSVerifier* iVerifier = [[NSVerifier alloc] init];
self.iVerifier = iVerifier;
[iVefirier show];
I think iVerifier is released after the code go out viewDidAppear.
Please try: handle the iVerifier by a property with strong reference.
I have a class that I instance to show an alert view like this:
- (void)showAlert
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Do you want to try again?"
message:nil
delegate:self
cancelButtonTitle:#"Yes"
otherButtonTitles:#"No", nil];
[alertView show];
}
}
I need self to be the delegate because I need alertView:didDismissWithButtonIndex: to be called to perform some actions when the user taps the alert view's button. This usually works well, but from time to time, I get this crash:
SIGSEGV
UIKit-[UIAlertView(Private) modalItem:shouldDismissForButtonAtIndex:]
I guess this is because the delegate, for any reason, was released, right? Or is this because what was released was the alert view? How could I solve this? I need the alert view to have a delegate, and I've reading several related posts and I couldn't find an answer that fits my scenario.
I'm testing in iOS 7.0, I don`t know if that could have to do with the issue.
Thanks in advance
It seems that you tap alert when its delegate is released:
delegate:self
It happens because UIAlertView delegate property is of assign type (not weak!).
So your delegate potentially can point to released object.
Solution:
in dealloc method you need to clear delegate for your alertView
- (void)dealloc
{
_alertView.delegate = nil;
}
But before you need to make iVar _alertView and use it for your alertViews
- (void)showAlert
{
_alertView = ...;
[_alertView show];
}
Update your code as follows:
- (void)showAlert {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Do you want to try again?"
message:nil
delegate:self
cancelButtonTitle:#"Yes"
otherButtonTitles:#"No", nil];
[alertView show];
}
Its due to you are missing the nil for otherButtonTitles part.
Missing sentinel in method dispatch warning will be shown if you didn't add nil.
That's because the alertView's delegate object released while clicking the button. I think it's a bug of SDK:
#property(nonatomic,assign) id /*<UIAlertViewDelegate>*/ delegate; // weak reference
should be:
#property(nonatomic, weak) id /*<UIAlertViewDelegate>*/ delegate; // weak reference
To fix the issue:
add a weak delegate for UIAlertView using association.
swizzle the init, setDelegate: delegate methods, set alertView delegate to self, set step 1 weak delegate with the param delegate.
implement all delegate methods, deliver the methods using the weak delegate.
I've got an issue where an object that's creating a UIAlertView is sending a message back to its delegate, but the delegate object is crashing.
I've got it working OK in a couple of other instances, but what I'm trying to do in this case (and what's different from other similar questions) is that the object that instantiates the alert, and which acts as its delegate is not itself a view, but rather an object that is instantiated within a view. To wit, in the parent view:
#implementation
*MyCustomObject customObject;
-(void)viewDidLoad {
customObject = [[MyCustomObject alloc] init];
}
#end
And in the custom object:
-(void)DoCoolThings {
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Do You Want To Do Cool Things"
message:#"...description of cool things..."
delegate:self
cancelButtonTitle:#"No Thanks"
otherButtonTitles:#"HELLS YES", nil];
[message show];
}
and
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 1) {
[self DoCoolThings];
} else {
[self GoAndCry];
}
}
When I do the alert view within a viewcontroller object, everything is fine. Doing it within this sub-object which itself has no view allows me to create the alert, but that object--which shouldn't be getting de-allocated based on the scoping--doesn't seem to want to continue to act as a delegate.
The error I'm getting is indeed a de-allocation message, but I feel strongly that this is not the problem because if I remove the alert, all the other stuff--specifically, it's a wrapper for a storekit purchase process--works fine, and all those delegate methods work happily.
I've got a solution which will allow me to move the Alert into the parent view's methods, but I was hoping not to have to. Is this limitation real, or is it my imagination? IE am I doing something else wrong?
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.
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]);
}