Delegate and Protocol code not entering delegate method - ios

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.

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?

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

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?

How can I do actions in buttons of a UIAlertView?

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.

UIAlertView Button action causing error

I am displaying a UIAlertView to the user with an "OK" button on it, and when the user presses the button I would like my delegate method to perform an action. However currently when the user presses the "OK" button the application crashes with this error:
Thread 1: EXC_BAD_ACCESS (code=1, address=0xb)
Here is my code, the alert view shows fine with the button etc, however as soon as the button is pressed this is the error I am getting. It's almost like a breakpoint but if I click the forward button nothing happens.
//.h
#interface ErrorHandling : NSObject <UIAlertViewDelegate> {
//.m
#define myAlertViewsTag 0
- (void)recivedErrorCode:(int)errorCode MethodName:(NSString *)methodName {
switch (errorCode) {
case 1: {
NSLog(#"ERROR = 1");
message = [[UIAlertView alloc] initWithTitle:#"Error 1:"
message:#"The supplied registration code does exist."
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[SVProgressHUD dismiss];
[message show];
}
break;
case 2: {
NSLog(#"ERROR = 2");
message = [[UIAlertView alloc] initWithTitle:#"Error 2:"
message:#"Your registration is no longer valid."
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
message.tag = myAlertViewsTag;
[SVProgressHUD dismiss];
[message show];
}
break;
default:
break;
}
}
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (message.tag == myAlertViewsTag) {
if (buttonIndex == 0) {
// Do something when ok pressed
NSLog(#"DONE 1");
} else {
// Do something for ok
NSLog(#"DONE 2");
}
} else {
// Do something with responses from other alertViews
NSLog(#"DONE 3");
}
}
update:
This is how I call the class the code above is in from my conneciton class.
// Do whatever you need to with the error number
ErrorHandling *errorHandling = [[ErrorHandling alloc] init];
[errorHandling recivedErrorCode:errorRecivedFromServerInt MethodName:methodName];
The problem is with the lifetime of your ErrorHandling instance. You create an instance and then call the recivedErrorCode: method. After that there are no other strong references to your errorHandling instance so it gets deallocated.
Meanwhile the alert view has made the instance its delegate. When you tap the button on the alert view, the alert view tries to contact its delegate. But the delegate has been deallocated and now points to garbage memory resulting in the crash.
The solution is to keep a longer lasting strong reference to the ErrorHandling instance. At least until after the alert view is dismissed.
BTW - Your method name has a typo - it should be receivedErrorCode:.
It is not at all clear from this code if this line:
if (message.tag == myAlertViewsTag)...
Actually points to anything valid. Is the message object still around even... I think you problems lies there, somewhere. Post more code that helps us understand this if you want more help.
In alertView:didDismissWithButtonIndex: you are checking the alert view's tag by using "message.tag".
It looks like you were intending for message to be an ivar in your class. A better way that should also solve your bad_access would be to use the alert view that is passed into the didDismissWithButtonIndex, rather than retaining the UIAlertView in your class. This code should do the trick:
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (alertView.tag == myAlertViewsTag) {
if (buttonIndex == 0) {
// Do something when ok pressed
NSLog(#"DONE 1");
} else {
// Do something for ok
NSLog(#"DONE 2");
}
} else {
// Do something with responses from other alertViews
NSLog(#"DONE 3");
}
}

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.

Resources