NSObject extending UIAlertViewDelegate crash? - objective-c

I have a helper class that I want to display a UIAlertView then will perform some web service action. I would like to do it this way so I can re-use the alert view in other places.
I am declaring it like so:
#interface FBLinkHelper : NSObject <UIAlertViewDelegate>
I also have a method that will show the alert view:
- (void) showLinkDialog {
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Link account with Facebook?" message:#"Linking your account with Facebook extends your user experience. Link account?" delegate:self cancelButtonTitle:#"No" otherButtonTitles:#"Yes", nil];
[av show];
}
This is how I am showing the dialog:
FBLinkHelper *fbLinkHelper = [[FBLinkHelper alloc] init];
[fbLinkHelper showLinkDialog];
All seems pretty normal to me. It shows the alert view dialog, but when I click a button (either one) my app crashes without much to say in the console. I notice it only crashes when I have a UIAlertViewDelegate method in place, such as didDismissWithButtonIndex. Why is this happening?

-UPDATE-
The FBLinker instance was not a strong property of its caller, so the delegate callback went to a dangling pointer. OP reports making FBLinker strong took care of issue.
Original suggestion of making the class extend from UIView instead of NSObject had no effect, and is referenced here only for historical purposes.

I had the same case in an app I'm working at. If the delegate object isn't a strong (or retain in iOS prior to 5.0) property it might get deallocated right after the UIAlertView is shown.

Related

How can I move this viewcontroller-reliant method over to a helper class?

I am using the Evernote API, and I have created a helper class that will contain the Evernote API-specific methods in an effort to keep code as abstracted as possible.
But there is one method that needs to be called from the view controller to show a login form, as well as an alertView if an error occurs. Here is the code:
- (IBAction)loginToEvernote:(id)sender {
EvernoteSession *session = [EvernoteSession sharedSession];
[session authenticateWithViewController:self completionHandler:^(NSError *error) {
if (error || !session.isAuthenticated) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Could not authenticate"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
} else {
}
}];
}
It seems to me the "authenticateWithViewController:completionHandler" method needs to be in a view controller, in order to show the modal view controller that contains the login information.
Again, this is a method from an API, so I cannot alter it.
How can I move this method over to the helper class? At first I thought delegation, but I am already making this view controller the delegate of the helper class in order to send other error messages to it, and making the helper class and viewcontroller delegates of each other seems code-smelly to me, if it's even possible at all.
If the Evernote API requires a view controller, and you want complete abstraction, I would have a method in the helper class like this something like this in the helper class:
- (void)authenticateWithViewController:(UIViewController *)viewController
In this method, you can encapsulate any shared behaviours, calling through to the Evernote session API passing the viewController parameter to the method.

Objective C - UIAlertViewDelegate cannot access UILabel in other class

I am new to Objective C and having an issue that I know must be a very simple one - I think perhaps I am approaching it the wrong way.
I have created an UIAlertViewDelegate class that I want to be the delegate for some UIAlertViews that I have in my View Controller class.
When the user presses a button and enters some text, I would like a label in my ViewController class to be updated with that text. However, I obviously cannot reference the label "outputLabel" from my UIAlertViewDelegate class unless I pass it in some way.
The error I get is "Use of undeclared identifier 'outputLabel'. "
ViewController.h
#interface ViewController : UIViewController <UIAlertViewDelegate>;
...
#property (strong) UIAlertViewDelegate *AVDelegate;
ViewController.m
- (void)viewDidLoad
{
UIAlertViewDelegate *AVDelegate;
AVDelegate = [[UIAlertViewDelegate alloc] init];
[super viewDidLoad];
}
- (IBAction)doAlertInput:(id)sender {
UIAlertViewDelegate *AVDelegate;
AVDelegate = [[UIAlertViewDelegate alloc] init];
UIAlertView *alertDialogue;
alertDialogue = [[UIAlertView alloc]
initWithTitle:#"Email Address"
message:#"Please Enter Your Email Address:"
delegate:self.AVDelegate
cancelButtonTitle:#"OK"
otherButtonTitles:nil, nil];
alertDialogue.alertViewStyle=UIAlertViewStylePlainTextInput;
[alertDialogue show];
}
UIAlertViewDelegate.m
#import "UIAlertViewDelegate.h"
#implementation UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *buttonTitle = [alertView buttonTitleAtIndex:buttonIndex];
NSString *alertTitle = [alertView title];
if ([alertTitle isEqualToString:#"Alert Button Selected"] || [alertTitle isEqualToString:#"Alert Button Selected"]) {
if ([buttonTitle isEqualToString:#"Maybe Later"])
{
outputLabel.text=#"User Clicked: 'Maybe Later'";
UIAlertViewDelegate.h
#interface UIAlertViewDelegate : UIViewController <UIAlertViewDelegate,UIActionSheetDelegate>;
I've done some work many years ago with Java. I imagine I have to pass my UIAlertViewDelegate class the view somehow but Im not sure on the syntax, would greatly appreciate a pointer in the right direction...
James
There are a variety of things here that you shouldn't be doing but, starting with the main ones affecting your question....
You shouldn't be inventing your own UIAlertViewDelegate class, which means don't do this...
#interface UIAlertViewDelegate : UIViewController <UIAlertViewDelegate,UIActionSheetDelegate>;
Because UIAlertViewDelegate is a protocol, you also can't use alloc with it.
The main thing that's correct is...
#interface ViewController : UIViewController <UIAlertViewDelegate>
That says that your ViewController is able to act as a UIAlertViewDelegate and therefore your code for creating the alert can use delegate:self instead of trying to create a separate delegate property or object.
You are likely to still have questions but those changes should clean it up a bit.
Your view controller is the delegate for the alert view, therefore, you don’t need to crate the delegate object.
init the alert view using self (the view controller) as the delegate argument.
You then implement the delegate methods (for the alert view) within the view controller.
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIAlertViewDelegate_Protocol/UIAlertViewDelegate/UIAlertViewDelegate.html
The point of delegation is to make it possible for an object to have callbacks on any class.
I would suggest reading over protocols and delegation.
http://developer.apple.com/library/ios/#documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html#//apple_ref/doc/uid/TP40008195-CH14-SW1
I'm guessing you are trying to use the alert view to input text, so you would set the alert view style to UIAlertViewPlainTextStyle, and setting the label in one of the callbacks by accessing the text field from the alertView object.

Prevent access to UIViewControllers with tab bar controller (storyboard)

I currently encountering a problem with my iOS application.
I am attempting to incorporate a gradual login pattern, i.e.: the use can access some of the app without being required to login.
Required features are as follows:
At all times the user can view all navigation items that require login
When the user attempts to access an uiview(controller) that requires login, they will be prompted with a UIAlertView asking them to log in. (Preferably the UIAlertView will appear when the app recognised the initiated segue destination is restricted).
At first I used a subclass of UIViewController that, in the designated initialiser (initWithCoder), would check NSUserDefaults to see if the user was logged in. I then subclassed off of that. Limitations were as follows:
Couldn't use other subclasses of UIViewController, namely UITableViewController
The UIAlertView came up after the view had appeared, which i am assuming would cause errors if the subclassed UIViewController assumed the user was logged in.
Question summary:
I would like to know how to conditionally prevent users from accessing certain UIView(Controller)s and subclasses of UIViewController, and when that happens present a UIAlertView.
Update 1
Could categories and/or protocols be a viable solution?
Update 2
CodaFi pointed out singletons as a great solution to manage the user's state.
With that implemented I now need to figure out how to control the user's access.
As I am using storyboards I feel that the most versatile implementation would be subclassing UIStoryboardSegue, and on the perform method check if the user is attempting to access an restricted UIViewController (perhaps restricted controllers have a protocol property that specifies the required status: logged in/out). However the pitfall here is that you cannot choose the class/subclass of a UIStoryboardSegue in the storyboard graphic editor. I am aware that I could do it programatically, however that seems tedious as i would have to add IBActions and like that to methods that perform segues, furthermore I don't think that would work with the way elements such as navigationController and tabbarControllers behave.
Does anybody have a viable solution to restricting the user's navigation?
Update 3
I've added an answer to this question, however I still deem it as unanswered because the answer I've written doesn't take into account segues between navigation bar controllers. However it may help some people.
So, I've answered how to do this using custom segues.
Now I understand my real problem was the mental disconnect with the tabbarcontrollerdelegate protocol.
As a solution to preventing access to tabs I've made a class to handle said tab bar controller
#import <Foundation/Foundation.h>
#interface TabBarDelegate : NSObject<UITabBarControllerDelegate,UIAlertViewDelegate>{
UITabBarController *cachedTabBarController;
}
#end
#import "TabBarDelegate.h"
#implementation TabBarDelegate
-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController{
NSLog(#"%s",__FUNCTION__);
NSLog(#"Pretending the user isnt logged in");
if(true){
NSString *message = [NSString stringWithFormat:#"You require an account to access %#",viewController.tabBarItem.title];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Account Required" message:message delegate:self cancelButtonTitle:#"Okay" otherButtonTitles: #"Login",#"Create Account",nil];
[alert show];
//Hold tabbarcontroller property for alert callback
cachedTabBarController = tabBarController;
return false;
}
return true;
}
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex{
if(cachedTabBarController){
switch (buttonIndex) {
case 1:
//Login
[cachedTabBarController performSegueWithIdentifier:#"tabBarToLogin" sender:cachedTabBarController];
break;
case 2:
//Sign up
[cachedTabBarController performSegueWithIdentifier:#"tabBarToSignup" sender:cachedTabBarController];
break;
default:
break;
}
//Release tab bar controller from memory
cachedTabBarController = nil;
}
}
#end
Then i wired it up to my appDelegate in applicationDidFinishLaunching...
//Hook up tab bar delegate
mainTabController = (UITabBarController*)self.window.rootViewController;
mainTabBarDelegate = [[TabBarDelegate alloc] init];
mainTabController.delegate = mainTabBarDelegate;
And Voila
Okay, So I have part of a solution.
It only works in situations where you can choose a custom segue (and I've only written code for pushing, not modal).
Protocol "UIViewControllerAuthentication" for all controllers that require authentication, this protocol contains an method to retrieve the required authentication status.
enum authenticationStatus {
notLoggedIn = 0,
noPassword = 1,
loggedIn = 2
};
#protocol UIViewControllerAuthentication <NSObject>
-(enum authenticationStatus)authStatusRequired;
#end
Controllers that require authentication conform to this protocol:
-(enum authenticationStatus)authStatusRequired{
return loggedIn;
}
Then use this for the authenticated push segue
#interface SeguePushWithAuth : UIStoryboardSegue
#end
-(void)perform{
NSLog(#"custom segue destination : %#",self.destinationViewController);
NSLog(#"custom segue source : %#",self.sourceViewController);
NSLog(#"custom segue dest conforms to protocol : %i",[self.destinationViewController conformsToProtocol:#protocol(UIViewControllerAuthentication)]);
if([self.destinationViewController conformsToProtocol:#protocol(UIViewControllerAuthentication)]){
UIViewController <UIViewControllerAuthentication> *destination = (UIViewController <UIViewControllerAuthentication> *)self.destinationViewController;
if (!((int)[AccountModel userAuthStatus] >= (int)[destination authStatusRequired])) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Authentication" message:#"You need an account to access this area" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Login",#"Create Account", nil];
//alert
[alert show];
return;
}
}
[[self.sourceViewController navigationController] pushViewController:self.destinationViewController animated:true];
}
#end
Then use custom segues in uistoryboard
This is a good pattern to allow the destination controller to cancel the segue.
However its far from what I want as I want to be able to authenticate segues that link tab bar controllers to their children.

show alert xcode and go back after alert

i want to show the alert and when somebody click on OK they need to be send to the page before. How can i make this?
I use the following code:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"BOOYAH!"
message:#"Saved" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
Considering you have 1 option on the alert view and the delegate is self. Use this method in the same .m file as the code above
- (void)alertView:(UIAlertView *)alertV didDismissWithButtonIndex:(NSInteger)buttonIndex
{
//go back a page
[alertV autorelease];
}
Don't forget to release the alert view. I added it in the delegate method, but you can choose to release it right after showing it (only 1 release though)
Assign the UIAlertViewDelegate to self and then implement the following method is called
- (void) alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
if (buttonIndex == buttonIndexForOK) { // Where buttonIndexForOK is the index for your ok button, in this case, that would be zero, but if you want an OK and a Cancel button this would be different.
// go back to the last page
}
}
UIAlertView follows the delegation design pattern that is extremely common in iOS development. You provide a delegate object, and when the object wants to tell you about something, it sends that delegate object a message.
In your code, you've provided self as the delegate. This means that this object needs to conform to the UIAlertViewDelegate protocol.
You will see that there are several methods you can implement to react to various events relating to the alert view. You should use the alertView:clickedButtonAtIndex: method, which provides an index parameter indicating which button was tapped.

How to release a "PopUp" view"?

I have this class that shows a popup.
I do a alloc-init on it and it comes up.
DarkVader* darkPopUp = [[DarkVader alloc] init:theButton helpMessage:[theButton.titleLabel.text intValue] isADay:NO offset:0];
It shows itself and if the user presses Ok it disappears. When do I release this?
I could do a [self release] in the class when the OK button is pressed. Is this correct?
If I do this the Analyzer says it has a retain count of +1 and gets leaked in the calling function.
If I release it just after the alloc-init the Analyzer says it has a retain count of +0 and i should not release it.
DLog(#"DarkVader retain count: %i", [darkPopUp retainCount]);
says it has a retain count of 2. I'm confused.
In short my question is: How do I release an object that gets initialized does some work and ends but no one is there to release it in the calling function.
My suggestion would be to use
[self autorelease];
when the view is closing itself. Although if you look at various standard views, then all implement callbacks to a delegate that becomes responsible for closing them; this let's the launching object be responsible for releasing the view as well. You also don't make it clear how your view (or is it a view controller) is displayed.
You could do something similar to what existing Cocoa Touch classes does. For example, see how you show an UIAlertView:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"title" message:#"message" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
[alert release];
It's quite clear that UIAlertView does a [self retain], or more likely, gets retained when added as a subview somewhere on the screen somewhere in the show method.
There are some Cocoa Touch classes that indeed (just as Paul mentioned) do not support this way of release at once, but instead calls a delegate method and excepts the receiver to release it.
I'd say the answer is, if your DarkVader is an UIView, you should let the subview-retain take care of the retain count. If it's a UIViewController or a custom helper class, you have a few options, the delegate way being a simple and straight forward one.
If you want a custom pop-up in the style you described you should probably already be subclassing UIAlertView to begin with. Then you can use it's already implemented retain/release functionality.