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

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...

Related

Equivalent Storyboard function for Nib

I'm trying to convert a project on macOS that uses Storyboards to instantiate a ViewController through a delegate, although I'm running into some difficulty trying to convert it to use a Nib instead.
Currently the storyboard version of the code uses an App Delegate which is associated with two View Controllers. When a button is clicked the front window animates and flips over revealing another (back) window. The code that instantiates the View Controller is:
mainWindow = [NSApplication sharedApplication].windows[0];
secondaryWindow = [[NSWindow alloc]init];
[secondaryWindow setFrame:mainWindow.frame display:false];
// the below is what I'm not sure of - how to reference nib instead of storyboard?
NSStoryboard *mainStoryboard = [NSStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]];
NSViewController *vc = [mainStoryboard instantiateControllerWithIdentifier:#"BackViewController"];
[secondaryWindow setContentViewController:vc];
I'm not sure the proper way to reference a nib instead of a storyboard in the example shown above. The project I'm trying to convert is located here. I'm really hoping someone can help, thank you!
This is pretty easy to do. Just make an NSViewController subclass (or an NSWindowController subclass if you want it to control a whole window) for each of the two views. For each view, override -init and have it call super's implementation of -initWithNibName:bundle: with the name of the view's nib file:
#implementation MyViewController
- (instancetype)init {
self = [super initWithNibName:#"MyViewController" bundle:nil];
if (self == nil) {
return nil;
}
return self;
}
Note that if you can require a sufficiently recent version of macOS (I think it's 10.11 and higher off the top of my head, but it's possible that I could be off by a version or so), you don't even have to do this much, because NSViewController will automatically look for a nib file with the same name as the class.
Anyway, now you should be able to instantiate a MyViewController and insert its view into your view hierarchy, and manipulate it the same way you'd manipulate any other view:
MyViewController *vc = [MyViewController new];
[someSuperview addSubview:vc.view];
If you want to do windows instead, you can make an NSWindowController subclass instead of NSViewController. NSWindowController is slightly more annoying to use, since its initializers that take nib names are all convenience initializers, whereas the designated initializer just takes an NSWindow. So if you're using, say, Swift, you can't do it the way I did it above with NSViewController. Objective-C, of course, generally lets you do whatever you want, so you actually can get away with just calling super's -initWithWindowNibName:owner:, and I won't tell anyone, wink wink, nudge nudge. However, to be stylistically correct, you probably should just call -initWithWindow: passing nil, and then override windowNibName and owner:
#implementation MyWindowController
- (instancetype)init {
self = [super initWithWindow:nil];
if (self == nil) {
return nil;
}
return self;
}
- (NSNibName)windowNibName {
return #"MyWindowController";
}
- (id)owner {
return self;
}
This should get you a window controller that you can just initialize with +new (or +alloc and -init if you prefer), then call its -window property and manipulate the window as normal.

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.

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;

Custom back button UINavigationController across my entire app

I am looking to replace the back button in the UINavigationController throughout my application. My requirements is that this back button be defined in one XIB and if possible, the code to set it is in one place.
I have seen various methods that set the property self.navigationItem.backBarButtonItem to be a UIBarButtomItem with the custom button as it's view, e.g. [[UIBarButtonItem alloc] initWithCustomView:myButton];
My first thought was to create a global category (not sure if that's the term, I'm new to Objective-C as you might have guessed) that implements 'ViewDidLoad' for all my UINavigationControllers, and setting this property. My problem is loading the XIB to this button that I create at runtime.
Does anyone have a suggestion on a neat way of doing this (I guess it must be a common thing to do, and I can't imagine repeating code in all my screens). I have considered creating a UINavigationController subclass, however I wasn't sure how this would effect my custom implementations of ViewDidLoad.
Any advice much appreciated. Also I need to target >= iOS4 (the appearance API is iOS5 only).
I prefer to not force inheritance where possible so you could do this with two categories
#interface UIViewController (backButtonAdditions)
- (void)ps_addBackbutton;
#end
#implementation UIViewController (backButtonAdditions)
- (void)ps_addBackbutton;
{
// add back button
}
#end
#interface UINavigationController (backButtonAdditions)
- (void)ps_pushViewController:(UIViewController *)viewController animated:(BOOL)animated;
#end
#implementation UINavigationController (backButtonAdditions)
- (void)ps_pushViewController:(UIViewController *)viewController animated:(BOOL)animated;
{
[viewController ps_addBackbutton];
[self pushViewController:viewController animated:animated];
}
#end
Now #import these files as appropriate and instead of using
[self.navigationController pushViewController:aViewController YES];
use
[self.navigationController ps_pushViewController:aViewController YES];
Disclaimer
I free styled this in the browser so you may need to tweak it
I had the same issue in my current project, the solution I came up with was to create a MYBaseViewController base class without xib and there in viewDidLoad programmatically (if you want to init barButtonItem with custom view, you are not able to create in xib anyways) create a customBackButton (and of course release it and set to nil viewDidUnload.
This works good for me because this way I can create xibs for all my other viewControllers that are subclasses of MYBaseViewController(if you created a view for base class in nib you would not be able to create a nib for a subclass).

Xcode 4.2 & Storyboard, how to access data from another class?

I was wondering how I could access data from another class using Xcode 4.2 and Storyboard?
Say for instance how would I access the text of a text field from another class?
Google hasn't helped and the lesson on MyCodeTeacher.com about this is outdated and doesn't work anymore...
Thanks for bearing with me!
-Shredder2794
Not sure if this is the only or best way, but you can create a property in the destination view's .h file and set it to a value before the segue is performed
in the destination view controller's .h file:
#interface YourDestinationViewController : UIViewController
{
NSString* _stringToDisplay;
//...
}
#property (nonatomic, retain) NSString* stringToDisplay;
//...
and in the presenting view's .m file
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
YourDestinationViewController*viewController = segue.destinationViewController;
viewController.delegate = self;
viewController.stringToDisplay = #"this is the string";
}
Then you can do what you want with the property in whichever of the viewWillAppear/viewDidLoad/viewDidAppear/etc. methods best suits your purpose in the destination view's .m file
And then to check if it works, in the destination view controller's .m file:
-(void)viewWillAppear:(BOOL)animated
{
NSLog(#"self.stringToDisplay = %#", self.stringToDisplay);
...
//and if a label was defined as a property already you could set the
//label.text value here
}
Edit: Added more code, and made it less generic
This isn't specific to Storyboard. There are several ways to do what you are trying to do. You could declare a variable in your AppDelegate (an NSString) and set that in your first class. Then in your second class access the AppDelegate variable and use that to set your label. The code to do this is:
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
label.text = appDelegate.myString;
Another way to do it (probably the easiest) is to declare an NSString in your second class. Then in your first class, before you push the second view set that string variable. Something like this:
MyViewController *vc = [[MyViewController alloc] initWithNibName:#"" bundle:nil];
vc.myString = #"";
The third way to do this is using delegates. This is the most 'complicated' way but is the best. You would create a delegate which gets called when your second view appears. The delegate could then return the value from the first class to you.
You may also be able to use the new completion handler block on the iOS 5 pushViewController: method.
Edit:
Custom init method:
- (void)initWithNibName:(NSString *)nibName bundle:(NSString *)bundle string:(NSString *)myString
And then when you are pushing the view just class this method and set the string through it.