Objective C - UIAlertViewDelegate cannot access UILabel in other class - objective-c

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.

Related

How Do I Know Which Methods to Override When Writing an Objective-C Category?

I want to write a category on UINavigationItem to make changes to barBackButtonItem across my entire app.
From what I have been told in the comments here ( Change BackBarButtonItem for All UIViewControllers? ), I should "override backBarButtonItem in it, then your method will be called whenever their back bar button item is called for." - but how do I know what method to override? I have looked at the UINavigationItem documentation, and there are multiple methods used for initializing a backBarButtonItem. How do I determine which method I should override in my category?
If you want to override backBarButtonItem, override backBarButtonItem. There is one and only one method called backBarButtonItem. ObjC methods are uniquely determined by their name.
You'd do it like so:
#implementation UINavigationItem (MyCategory)
- (UIBarButtonItem *)backBarButtonItem
{
return [[UIBarButtonItem alloc] initWithTitle:nil style:UIBarButtonItemStylePlain target:nil action:nil]
}
#end
I'm not saying it's a good idea, but that's how you'd do it.
You want a subclass of UIViewController instead of a catagory.
For example:
#interface CustomViewController : UIViewController
#end
#implementation CustomViewController
-(void) viewDidLoad {
[super viewDidLoad];
self.navigationItem.backBarButtonItem.title = #"";
}
#end
Now you just need to use the CustomViewController class for your view controllers, and they will all have the changes applied to them.
If you're doing this programatically, then you'll just want to change the superclass of the view controllers:
From this.... to this...
If you're using storyboards, you'll want to change the superclass from within the Identity Inspector...

Push to UIViewController dynamically

I am a newbie in iPhone application development.
I am developing an iPad application. It contains a menu bar on top, clicking on which retrieves a sub view. The sub view consists of UIPickerView. Upon selecting a row from UIPickerView, navigates to another UIViewController.
The UIPickerView methods are written in a separate class (As this functionality comes throughout the app, I made it a general one). So,
[self.navigationController pushViewController:controller animated:YES];
will not work for me!
I was able to get the name of the class to be pushed (It changes according to the selection made). Is there any way I can do it?
Thanks In Advance :-)
I guess what you really want is to create an object from a classname
The simple answer is
[[NSClassFromString(className) alloc] init...]
For a more thorough answer you should look at Create object from NSString of class name in Objective-C
You can use delegate method (delegate methods allows communication between objects) to implement this scenario
For example in your UIPicker(.h) class define a delegate protocol as follows
#protocol pickerProtocol;
#interface MyPicker : NSObject {
id <pickerProtocol> pickerDelegate;
}
#property(nonatomic,retain) id <pickerProtocol> pickerDelegate;
#end
#protocol pickerProtocol
- (void) pushViewController;
#end
And call this delegate method when selecting a row from UIPickerView
[pickerDelegate pushViewController];
Then in all view controller that uses picker write the implementation of the delegate method
- (void) pushViewController {
[self.navigationController pushViewController:controller animated:YES];
}
dont for get to set the delegate as follows
MyPicker *picker = [MyPicker alloc]init];
picker.pickerDelegate = self;

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.

How to present a view controller from another view controller

I am trying to open a ViewController from within another ViewController if certain conditions are met. The code seems to run without error but the view is never shown. I am new to xcode 4 /ios 5 so I must be missing something.
Here is the code responsible for opening the second viewcontroller:
CreateUserViewController *createUserController = [[CreateUserViewController alloc] initWithNibName:#"CreateUserView" bundle:[NSBundle mainBundle] keyWrapper:keyChainWrapper];
[self presentViewController:createUserController animated:YES completion:nil];
In my project I have a xib called, "CreateUserView". I have added a view controller to this xib and assigned it to, "CreateUserViewController".
Also I noticed in the apple documentation that is shows setting the delegate of the viewcontroller to be presented. But it seems that no property called, "delegate" is on the viewcontroller object. Is this documentation old? This is the document I am trying to use (section 9-1):
View Controller Programming
Can someone give me a hint? Thanks..
edit Adding Custom Constructor
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil keyWrapper:(KeychainItemWrapper *)keyWrapper
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if(self){
[self setKeyChainWrapper:keyWrapper];
}
return self;
}
Regarding CreateUserView.xib: you don't want to put a CreateUserViewController object in the nib. You want to set the custom class of the File's Owner placeholder to CreateUserViewController. Then you need to connect the view outlet of File's Owner to the top-level view in the nib.
Regarding the delegate property: The UIViewController class doesn't have its own delegate property. The idea is that you add a delegate property to your subclass of UIViewController. The delegate provides a way for your presented view controller to pass custom information back to the presenting view controller.
Why would you want to do that? Let's consider the code you posted. I'll assume you have a UserListViewController that shows a list of User objects, and has a "Create new user" button. When the user touches the "Create new user" button, you create a CreateUserViewController and present it.
The user interacts with the CreateUserViewController to set the attributes of the new User object - name, rank, hairstyle, etc. Then he touches a "Done" button. Your CreateUserViewController creates the new User object and puts it in the database. Then it needs to dismiss itself, so the UserListViewController's list of User objects will appear again.
But you want the User list to include the newly created User object and you want to scroll the list so that the new User is on the screen. So you need a way to have your CreateUserViewController tell the UserListViewController about the newly created User object. This is where the delegate comes in.
You define a protocol like this:
#protocol CreateUserViewControllerDelegate
- (void)didCreateUser:(User *)user;
#end
and you give your CreateUserViewController a delegate property:
#interface CreateUserViewController
#property (weak, nonatomic) id<CreateUserViewControllerDelegate> delegate;
// ...
When your CreateUserViewController's "Done" button is touched, you notify your delegate of the new User:
- (IBAction)doneButtonWasTouched:(id)sender {
User *user = [self createUser];
[self.delegate didCreateUser:user];
[self dismissViewControllerAnimated:YES completion:nil];
}
In your UserListViewController, you adopt and implement the protocol:
#interface UserListViewController <CreateUserViewControllerDelegate, UITableViewDelegate, UITableViewDataSource>
// ...
#end
#implementation UserListViewController
- (void)didCreateUser:(User *)user {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[self.users count] inSection:0];
[self.users addObject:user];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation: UITableViewRowAnimationAutomatic];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition: UITableViewScrollPositionNone animated:YES];
}
and when you need to present a CreateUserViewController, you set the new controller's delegate to the UserListViewController:
- (IBAction)createUserButtonWasTouched:(id)sender {
CreateUserViewController *createUserController = [[CreateUserViewController alloc] initWithNibName:#"CreateUserView" bundle:[NSBundle mainBundle] keyWrapper:keyChainWrapper];
createUserController.delegate = self;
[self presentViewController:createUserController animated:YES completion:nil];
}
In iOS5 the method for pushing new view controllers was really changed around quite a bit from iOS4 and Xcode 3. In summary, storyboards are now used to create your application view controller flow. Even though you may use standalone .xib files to build an application it is much less common in iOS5.
Anyway, the main method for pushing new view controllers onto the screen is done using segues. Check out this tutorial for an introduction: http://www.raywenderlich.com/5138/beginning-storyboards-in-ios-5-part-1
It does a good job on explaining how to create a storyboard and use segues. You can still present view controllers in code "the old way" but it is much much less common now with the introduction of these new technologies. There are also some absolutely awesome tutorials on iTunes U - search for CS193P. It's the Stanford Introductory class to Objective-C and programming for iOS. This should get you started and maybe help you think of a way to push your createUserController in a way more up to speed with iOS5.
UPDATE
I just wanted to add. If you configure your program to use storyboards and segues you can use the method performSegueWithIdentifier:sender: to perform the segue to your createUserController view if the proper conditions are met. See the Apple API for UIViewController for information on how to use this method.

UIViewController parentViewController access properties

I know this question has been asked several times and I did read existing posts on this topic but I still need help.
I have 2 UIViewControllers - parent and child. I display the child UIViewController using the presentModalViewController as below:
ChildController *child =
[[ChildController alloc] initWithNibName:#"ChildView" bundle:nil];
[self presentModalViewController:child animated:YES];
[child release];
The child view has a UIPickerView. When user selects an item from UIPickerView and clicks done, I have to dismiss the modal view and display the selected item on a UITextField in the parent view.
In child's button click delegate, I do the following:
ParentController *parent =
(ParentController *)[self.navigationController parentViewController];
[parent.myTextField setText:selectedText];
[self dismissModalViewControllerAnimated:YES];
Everything works without errors. But I don't know how to load the parent view so that it displays the updated UITextField.
I tried
[parent reloadInputViews];
doesn' work. Please help.
Delegation is the way to go. I know some people that may be looking for an easier solution but trust me I have tried others and nothing works better than delegation. So anyone having the same problem, go read up on delegation and follow it step by step.
In your subviewcontroller.h - declare a protocol and declare delegate mthods in it.
#protocol myDelegate
-(void)clickedButton:(subviewcontroller *)subController;
#end
In your subviewcontroller.h, within #interface:
id<myDelegate> delegate;
#property (nonatomic, assign) id<myDelegate> delegate;
NSString *data;
-(NSString *)getData;
In your subviewcontroller.m, synthesize myDelegate. Add the following code to where you want to notify your parentviewcontroller that the subview is done doing whatever it is supposed to do:
[delegate clickedButton:self];
and then handle getData to return whatever data you want to send to your parentviewcontroller
In your parentviewcontroller.h, import subviewcontroller.h and use it's delegate
#import "subviewcontroller.h"
#interface parentviewcontroller : VUIViewController <myDelegate>
{}
In your parentviewcontroller.m, implement the delegate method
- (void)clickedButton:(subviewcontroller *)subcontroller
{
NSString *myData = [subcontroller getData];
[self dimissModalViewControllerAnimated:YES];
[self reloadInputViews];
}
Don't forget memory management!
If a low-memory warning comes in during your modal view's display, the parent's view will be unloaded. Then parent.myTextField is no longer referring to the right text field until the view is reloaded. You can force a reload of the view just by calling parent.view;
However, a better idea might be to have the parent view have a String property that can be set by the child view. Then, when the parent view reappears, put that data into the text field, inside viewWillAppear: for example. You'd want to have the value set to some default value for when the parent view initially shows up too.
-(void) viewWillAppear:(BOOL) animated doesn't get called for me either, exactly when it's a modal view controller. No idea why. Not incorrectly overridden anywhere in this app, and the same problem occurs on the other 2 apps I'm working on. I really don't think it works.
I've used the delegate approach before, but I think that following approach is pretty good as well.
I work around this by adding a private category to UIViewController, like so:
.h file:
#interface UIViewController(Extras)
// returns true if this view was presented via presentModalViewController:animated:, false otherwise.
#property(readonly) BOOL isModal;
// Just like the regular dismissModalViewController, but actually calls viewWillAppear: on the parent, which hasn't been working for me, ever, for modal dialogs.
- (void)dismissModal: (BOOL) animated;
#end
and .m file:
#implementation UIView(Extras)
-(BOOL) isModal
{
return self == self.parentViewController.modalViewController;
}
- (void)dismissModal: (BOOL) animated
{
[self.parentViewController viewWillAppear: animated];
[self dismissModalViewControllerAnimated: animated];
}
#end
which I can now call like this when I want to dismiss the dialog box:
// If presented as a modal view, dismiss yourself.
if(self.isModal)
[self dismissModal: YES];
and now viewWillAppear is correctly called.
And yes, I'm donating a bonus 'isModal' property, so that the modal view can tell how it was being presented, and dismiss itself appropriately.