Does a UIAlertView's Delegate have to be a view controller? - ios

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?

Related

Strong reference to `self` to keep the object alive (temporarily): evil?

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?

UIAlertView not dismissing?

I have a few ViewController subclasses inside a UINavigation controller. I have successfully used UIAlertViews elsewhere in the application, and I know how to set the delegate and include the correct delegate methods, etc.
In a ViewController with a UITableView, I have implemented a 'pull to refresh' with a UIRefreshControl. I have a separate class to manage the downloading and parsing of some XML data, and in the event of a connection error, I post a notification. The view controller containing the table view observes this notification and runs a method where I build and display an alert:
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Connection Error" message:[[notification userInfo] objectForKey:#"error"] delegate:self cancelButtonTitle:#"Close" otherButtonTitles:nil];
alertView.alertViewStyle = UIAlertViewStyleDefault;
[alertView show];
The alert displays correctly, but the cancelButton is unresponsive - there is no way to dismiss the alert! Putting similar code (identical, but without the notification's userinfo) in the VC's viewDidLoad method creates an alert that behaves normally.
Is the refresh gesture hogging first responder or something? I have tried [alertView becomeFirstResponder]. I would be grateful for any advice…
Update: screenshot included… is this the right info? (can't embed this image for lack of reputation) http://i.stack.imgur.com/4CGqS.png
Edit
It seems like you have a deadlock or your thread is stuck waiting. You should look at your code and see what causes this.
Original answer which lead to update in OP
Make sure the alert is shown on the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
//Open alert here
});
This isn't a solution per se, but I might try two quick things as you troubleshoot:
1) Hardcode some text in the UIAlert, rather than passing in the notification object. See if there is any change in behavior.
2) Try adding another button to the alert and an accompanying method to catch it. So you'll see if the delegate is getting ny buttons messages at all.
Try adding a tag to the alertView
alertView.tag = 0;
Then create the method alertView:clickedButtonAtIndex: in the view controller.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (alertView.tag == 0) {
[alertView dismissWithClickedButtonIndex:0 animated:YES];
}
}

`UIAlertView` appears multiple times

I display an alert view in the didConnect method for bluetooth. For some reason, it fires four times. I'm trying to bandaid it and it is not working too well. Basically, I put the alert view in a method of its own, and call that method in didConnect. That's when it fires four times. I'm trying to set it up to only fire once. What I tried to do was set the alert views method to return a TRUE value. Then I do this:
if ([successfulConnection self] == FALSE) {
[self successfullConnection];
}
This works great the first time, but then the method is set to TRUE for the remainder of the time. I have the feeling that if I set it back to equal FALSE at the end of the if statement, then it will fire four times and I'll be right back where I started. Does anyone know how to change the above code to have it only fire once when it tries to fire four times?
Also tried replacing the above code with this in my didConnect, but it never fired at all:
[successfulConnection self];
if (successfulConnection.visible == YES) {
[successfulConnection dismissWithClickedButtonIndex:0 animated:YES];
}
If you call successfulConnection from your didConnect method, I think this should work (myAlert is the property name for the alert view):
-(void)successfulConnection {
if (! self.myAlert) {
self.myAlert = [[UIAlertView alloc]initWithTitle:#"ttile" message:#"message" delegate:self cancelButtonTitle:#"cancel" otherButtonTitles: nil];
[self.myAlert show];
}
}
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
self.myAlert = nil;
//do whatever with the result
}
The simplest thing to do is to just have a boolean that gets set to true when the UIAlertView is displayed, and then false when the UIAlertView is dismissed. Then, whenever you are going to show the UIAlertView, first check if it is already displaying.
These are methods you can use according to your requirement:
EDIT : Perfect way... if you dont want to upload your app on App Store
To know that alertView is currently visible or not.
Usage : Display alertView only if necesary other its already present.
-(UIAlertView *)getLastAlertView
{
Class UIAlertManager = objc_getClass("_UIAlertManager");
UIAlertView *topMostAlert = [UIAlertManager performSelector:#selector(topMostAlert)];
return topMostAlert;
}
Dissmiss any alertView present which you don't know.
Usage : dissmiss all alertView and then present new one
-(void)dissmissLastAlert
{
Class UIAlertManager = objc_getClass("_UIAlertManager");
UIAlertView *topMostAlert = [UIAlertManager performSelector:#selector(topMostAlert)];
if (topMostAlert) {
[topMostAlert dismissWithClickedButtonIndex:0 animated:YES];
}
}

Delay the presentation of UIAlertView until its associated viewController is displayed

I have a download running in background. It shows an UIAlertView under some fail condition.
When this alert happens, the application can be in any of the views it shows to the user, but only should be visible in one of them.
Can I delay the presentation of the UIAlertView to the moment the viewController it is associated with is displayed to the user (it's viewDidAppear method is invoked)?
Declare a property on the view controller that you want to show the view.
#interface DownloadViewController : UIViewController
{
UIAlertView *downloadAlertView;
}
#property (retain) UIAlertView *downloadAlertView;
#end
Then, when you detect the error, set the downloadAlertView property of the view controller (this will require you keeping a reference to this view controller by the object that is doing the downloading).
- (void)downloadFailed
{
UIAlertView *alertView = [[[UIAlertView alloc] init] autorelease];
alertView.title = #"Download Failed";
downloadViewController.downloadAlertView = alertView;
}
Then in your DownloadViewController implementation,
- (UIAlertView *)downloadAlertView
{
return downloadAlertView;
}
- (void)setDownloadAlertView:(UIAlertView *)aDownloadAlertView
{
// standard setter
[aDownloadAlertView retain];
[downloadAlertView release];
downloadAlertView = aDownloadAlertView;
// show the alert view if this view controller is currently visible
if (viewController.isViewLoaded && viewController.view.window)
{
[downloadAlertView show];
downloadAlertView = nil;
}
}
- (void)viewDidAppear
{
if (downloadAlertView)
{
[downloadAlertView show];
downloadAlertView = nil;
}
}
Quick explanation:
the first two methods are standard getter/setters, but the setter has added logic, so that if the view controller is currently visible, the alert is shown immediately.
if not, the alert view is stored by the view controller and shown as soon as the view appears.

AlertView to Pause Code

This seems to be a white elephant for the iPhone. I've tried all sorts, even loops and I can't make it work.
I have a view that loads a table. However I've moved into object archiving and for development purposes I want to have an initial AlertView that asks if a user wants to use a saved database or download a fresh copy
(void)showDBAlert {
UIActionSheet *alertDialogOpen;
alertDialogOpen = [[UIActionSheet alloc] initWithTitle:#"DownloadDB?"
delegate:self
cancelButtonTitle:#"Use Saved"
destructiveButtonTitle:#"Download DB"
otherButtonTitles:nil];
[alertDialogOpen showInView:self.view];
}
I'm using an ActionSheet in this instance. And I have implemented the protocol:
#interface ClubTableViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource, UIActionSheetDelegate>
Based on this I run this to check what button was pressed:
(void)actionSheet:(UIActionSheet *) actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
NSString *buttonTitle=[actionSheet buttonTitleAtIndex:buttonIndex];
if ( [buttonTitle isEqualToString:#"Download DB"] ) {
[self loadDataFromDB];
}
if ([buttonTitle isEqualToString:#"Use Saved"] ) {
self.rows = [NSKeyedUnarchiver unarchiveObjectWithFile:[self archivePath]];
if (self.rows == nil) {
[self loadDataFromDB];
}
}
}
The problem is my code to build the table executes before the user has made there choice. This causes all sorts of havok. As a result, how can I pause the code until a user has made there choice?
If you are using UITableView, you will not be able to "pause" the code, however this wouldn't be the method of choice anyways. A possible solution would be to load an empty table (return 0 in (UITableView *)tableView:numberOfRowsInSection) until the user has selected something, then reload the tableView's data with [tableView reloadData];
In your tableView:numberOfRowsInSection: just return zero until the user has made a decision.
Add [self.tableView reloadData]; at the end of the actionSheet delegate method. This will trigger all UITableViewDataSource methods again.

Resources