Call super in overridden class method [duplicate] - objective-c

This question already has answers here:
How to call original implementation when overwriting a method with a category?
(3 answers)
Closed 8 years ago.
I want to add a new custom UIButtonType to the UIButton class via a category like so:
enum {
UIButtonTypeMatteWhiteBordered = 0x100
};
#interface UIButton (Custom)
+ (id)buttonWithType:(UIButtonType)buttonType;
#end
Is it possible to get the super implementation of that overridden method somehow?
+ (id)buttonWithType:(UIButtonType)buttonType {
return [super buttonWithType:buttonType];
}
The code above is not valid since super refers to UIControl in this context.

You can replace the method at runtime with your own custom method like so:
#import <objc/runtime.h>
#implementation UIButton(Custom)
// At runtime this method will be called as buttonWithType:
+ (id)customButtonWithType:(UIButtonType)buttonType
{
// ---Add in custom code here---
// This line at runtime does not go into an infinite loop
// because it will call the real method instead of ours.
return [self customButtonWithType:buttonType];
}
// Swaps our custom implementation with the default one
// +load is called when a class is loaded into the system
+ (void) load
{
SEL origSel = #selector(buttonWithType:);
SEL newSel = #selector(customButtonWithType:);
Class buttonClass = [UIButton class];
Method origMethod = class_getInstanceMethod(buttonClass, origSel);
Method newMethod = class_getInstanceMethod(buttonClass, newSel);
method_exchangeImplementations(origMethod, newMethod);
}
Be careful how you use this, remember that it replaces the default implementation for every single UIButton your app uses. Also, it does override +load, so it may not work for classes that already have a +load method and rely on it.
In your case, you may well be better off just subclassing UIButton.
Edit: As Tyler notes below, because you have to use a class level method to make a button this may be the only way to override creation.

Jacob has a good point that category methods act differently than subclass methods. Apple strongly suggests that you only provide category methods that are entirely new, because there are multiple things that can go wrong otherwise - one of those being that defining a category method basically erases all other existing implementations of the same-named method.
Unfortunately for what you're trying to do, UIButton seems to be specifically designed to avoid subclassing. The only sanctioned way to get an instance of a UIButton is through the constructor [UIButton buttonWithType:]. The problem with a subclass like Jacob suggests (like this):
#implementation MyCustomButton
+ (id)buttonWithType:(UIButtonType)buttonType {
return [super buttonWithType:buttonType]; //super here refers to UIButton
}
#end
is that the type returned by [MyCustomButton buttonWithType:] will still be a UIButton, not MyCustomButton. Because Apple hasn't provided any UIButton init methods, there's not really a way for a subclass to instantiate itself and be properly initialized as a UIButton.
If you want some customized behavior, you can create a custom UIView subclass that always contains a button as a subview, so that you can take advantage of some of UIButton's functionality.
Something like this:
#interface MyButton : UIView {}
- (void)buttonTapped;
#end
#implementation MyButton
-(id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = self.bounds;
[button addTarget:self action:#selector(buttonTapped)
forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
return self;
}
- (void)buttonTapped {
// Respond to a button tap.
}
#end
If you want the button to do different things depending on more complex user interactions, you can make more calls to [UIButton addTarget:action:forControlEvents:] for different control events.
Reference: Apple's UIButton class reference

No, this is not possible when you use a category to augment a class' functionality, you are not extending the class, you are actually wholly overriding the existing method, you lose the original method completely. Gone like the wind.
If you create a subclass of UIButton, then this is totally possible:
enum {
UIButtonTypeMatteWhiteBordered = 0x100
};
#interface MyCustomButton : UIButton {}
#end
#implementation MyCustomButton
+ (id)buttonWithType:(UIButtonType)buttonType {
return [super buttonWithType:buttonType]; //super here refers to UIButton
}
#end

Related

Why is this delegate method automatically called in Objective-C?

I'm going through this book called "cocoa programming for mac os x" and I just started with delegates. This whole thing with delegates is still a little bit wacky to me but I think I just need to let it settle.
However there was this one exercise where I should implement a delegate of the main window so that if resized height is always 2xwidth.
So I got 4 files:
AppDelegate.h
AppDelegate.m
WindowDelegate.h
WindowDelegate.m
AppDelegate are just the two standard files that get created when you open a new Cocoa project. I had to look up the solution because I didn't quite know how to accomplish this task.
The solution was just to create a new cocoa class, "WindowDelegat.h/.m" and add this to it's implementation file:
- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize {
NSSize newSize = frameSize;
newSize.height = newSize.width * 2;
return newSize;
}
Then I opened the interface builder, added a new object and made it my WindowDelegate. I then had to ctrl drag from the WindowDelegate to the actual window and made it the window's delegate.
Clicked run and it worked. Yay! But why?
First I thought that "windowWillResize" is just one of these callback functions that get's called as soon as the window is resized but it isn't. Normally methods get invoked because the general lifecycle of an program invokes them or because they are an #IBAction, a button or different control elements.
But "windowWillResize" is non of them. So why is it called?
EDIT: Problem solved! Thanks a lot!
Now I'm trying to connect the delegate to the window programmatically. Therefore I deleted the referencing outlet from WindowDelegate to the actual window in interface builder. It works but I just want to verify that this it the correct way how it's done:
AppDelegate.h
#import <Cocoa/Cocoa.h>
#import "WindowDelegate.h"
#interface AppDelegate : NSObject <NSApplicationDelegate>
#end
AppDelegate.m
#import "AppDelegate.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (strong) WindowDelegate *winDeleg;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (void)awakeFromNib {
[_window setOpaque:NO];
NSColor *transparentColor = [NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.5];
[_window setBackgroundColor:transparentColor];
NSSize initialSize = NSMakeSize(100, 200);
[_window setContentSize:initialSize];
_winDeleg = [[WindowDelegate alloc] init];
[_window setDelegate: _winDeleg];
}
#end
WindowDelegate.h
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#interface WindowDelegate : NSObject <NSWindowDelegate>
#end
WindowDelegate.m
#import "WindowDelegate.h"
#implementation WindowDelegate
- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize {
NSSize newSize = frameSize;
newSize.height = newSize.width * 2;
return newSize;
}
- (id)init {
self = [super init];
return self;
}
#end
Why does the #property of WindowDelegate need to be strong?
And isn't my winDeleg an object? Why do I have to access it through _winDeleg when it's an object. I though the underscore is used to access variables?
Thank you for your help!
Clicked run and it worked. Yay! But why?
Because instances of NSWindow have a delegate property that can point to any object that implements the NSWindowDelegate protocol, and that protocol includes the -windowWillResize:toSize: method.
Read that a few times. The reason it's important is that you can create your own object, say that it implements NSWindowDelegate, implement -windowWillResize:toSize:, and set that object as your window's delegate. Then, whenever the user resizes the window, your method will be called and can modify the proposed new size.
Normally methods get invoked because the general lifecycle of an program invokes them or because they are an #IBAction, a button or different control elements. But "windowWillResize" is non of them. So why is it called?
This really isn't so different. Think of delegates as "helper objects." They let you customize the behavior of an object without having to create a whole new subclass. The NSWindowDelegate object is essentially a contract that the NSWindow promises to follow: whenever certain things happen, such as the user resizing the window, the window will call certain methods in its delegate object, if the delegate exists and implements those methods. In the case of NSApplication, a lot of those delegate methods are application lifecycle events, like the app starting up or quitting or getting a message from the operating system. In the case of NSWindow, delegate methods correspond to interesting events that can happen to a window, like the user moving it, hiding it, showing it, maximizing it, moving it to a different screen, etc. Other classes, like text views or network connections or movie players, have their own sets of interesting events and their own delegate protocols to match.
Note that methods marked IBAction really aren't delegate methods, they're just methods that get called by objects like controls that use a target/action paradigm. The IBAction keyword lets the IDE know which methods it should present as possible actions for things like buttons. You often find actions in window controllers and view controllers, and those objects frequently act as a delegate for some other object, but the actions themselves aren't part of the delegate protocol. For example, NSTableView takes a delegate object that determines how the table will act and what's displayed in it. It often makes sense for the view controller that manages the table to be the table's delegate, and that same view controller might also manage some buttons and contain the action methods that said buttons trigger, but the actions aren't part of the NSTableViewDelegate protocol and you therefore wouldn't call them delegate methods.

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

Creating an instance of an instance of another class

I'm trying to break some of my "super objects" into more manageable classes that have single (or at least limited) responsibility.
One problem I've just run into is making an object of a specific instance of a UIBarButtonItem. In the class it is in now I first define a UIButton, and then all of the images that act as icons for that button as subviews (for instance the button represents access/control to a device, and I use the button image to show the current signal strength of that device). Also that button is listening for NSNotifications from the device object to represent the signal strength changing, or if the device disconnects. And pressing the button sends a message to the device to disconnect. All of this code works perfectly fine now as a property of the RootViewController. However, I want to pull it out into its own class as the button is shared by several classes, and it just clutters up the controller with unnecessary methods.
I tried making an separate class with an init like below. However, this doesn't work as the self used for the button isn't the same self that is ultimately created by [UIBarButtonItem alloc] and when either the NSNotification or the button press try to send a message to the selector of "self", that object has already been dealloced. The problem is, I'm not sure how to create a object (as defined by the class) that is just an instance of another class, as opposed to a property of an object (as it currently is for the RootViewController).
Edit and additional explanation of my problem
MyClass is currently subclass of UIBarButtonItem. However, I'm not trying to use it like this: [[MyClass alloc] initWithCustomView:]. I want [MyClass alloc] init] by itself to completely create the custom view - in other words the whole point of this class is to completely contain all that is necessary for this button to create itself, manage its subviews, and take the appropriate action when it is pressed. (I could easily make MyClass an NSObject with a public method like [MyClass setupButton] and a public property of type UIBarButtonItem. However, I think that looks wrong because then the class is only there to create the button, but it is not the button itself.)
#interface MyClass : UIBarButtonItem
#end
#implementation MyClass
- (id)init {
if (self = [super init]) {
UIImage *defaultButton = [[UIImage imageNamed:#"...
UIImage *defaultButtonPressed = [[UIImage imageNamed:#"....
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 40, 30)];
[button setBackgroundImage:defaultButton forState:UIControlStateNormal];
[button setBackgroundImage:defaultButtonPressed forState:UIControlStateHighlighted];
[button addTarget:self action:#selector(deviceButtonPressed) forControlEvents:UIControlEventTouchUpInside];
//Then several UIImageViews that are added as subviews of the button, initially hidden
//Then set up the NSNotification listener
//Finally
self = [[UIBarButtonItem alloc] initWithCustomView:button];
}
return self;
}
//Then several functions to handle hiding and unhiding the subviews depending on the received device notifications, and a function to handle the button press and sending the message back to the device.
This is not how initialization works in Cocoa. Please read "Initialization" in the Cocoa Core Competencies guide.
Your object has already been allocated when this init method is run. You should not be reassigning the self pointer to another allocation.
Your class should first call its superclass's designated initializer self = [super initWithWhatever:obj];, then set up its own properties.
It seems to me that you want to extend UIBarButtonItem, not create an instance of it in your init method. Try changing your class declaration (in your class's .h file) from this:
#interface MyClass : NSObject
to this:
#interface MyClass : UIBarButtonItem
Then just return self in your init method. Setting self to a value is usually a bad idea.
If you're unsure about what's going on here, you're creating a subclass of UIBarButtonItem. This lets your subclass extend the superclass's functionality. If you're confused, you should take a look at subclassing/class inheritance in object-oriented languages to understand what's going on. This guide documents how classes work in Objective-C.

Without subclassing a UIView or UIViewController: possible to catch if a subview was added?

Is there a way to catch an event or notification if a view was added as subview to an existing view of a controller? I have a library here and I cannot subclass but need to know if a specific subview was added to trigger a custom action.
Is there a chance?
I will try adding a category for the didAddSubview method.
EDIT
Category is an alternative to subclassing so you could use something along those lines:
.h:
#import <UIkit/UIKit.h>
#interface UIView (AddSubView)
- (void)didAddSubview:(UIView *)view
#end
.m:
#implementation UIView (AddSubView)
- (void)didAddSubview:(UIView *)view
{
[self addSubview: view];
// invoke the method you want to notify the addition of the subview
}
#end
Not that I think this method is cleaner than the one #tiguero suggested, but I think it's slightly safer (see why using categories could be dangerous in my comments to his answer) and offers you more flexibilty.
This is somehow, although not exactly, but more at the conceptual level, the same way KVO works. You basically, dynamically alter the implementation of willMoveToSuperview and add your notification code to it.
//Makes views announce their change of superviews
Method method = class_getInstanceMethod([UIView class], #selector(willMoveToSuperview:));
IMP originalImp = method_getImplementation(method);
void (^block)(id, UIView*) = ^(id _self, UIView* superview) {
[_self willChangeValueForKey:#"superview"];
originalImp(_self, #selector(willMoveToSuperview:), superview);
[_self didChangeValueForKey:#"superview"];
};
IMP newImp = imp_implementationWithBlock((__bridge void*)block);
method_setImplementation(method, newImp);

set placeholder for uitextview methods are not being called

I saw this answer of how to create a placeholder for UITextView.
I took the following steps:
Add to the .h class the declaration:
#interface AdjustPhotoViewController : UIViewController<UITextViewDelegate>
Added the method:
- (BOOL) textViewShouldBeginEditing:(UITextView *)textView
{
NSLog(#"%d",[textView tag]);
if ([textView tag]==1){
campaignTitle.text = #"";
}else{
campaignDescription.text = #"";
}
return YES;
}
But I don't see that the method is being invoked!
What am I missing?
textView is already delegated via the storyboard to the view
SOLVED:
The problem was that it wasn't delegated. Although I was using storyboard - it was only an outlet, not a delegate.
Remember that if you are using storyboard, you need to delegate also from the text view to the orange button of the view! not only the other way
What am I missing?
Actually setting the delegate.
textView.delegate = self;
Merely conforming to a protocol won't magically make your object into the delegate of an arbitrary object; that's just a formal thing, and anyways, how on Earth would the UITextField know which particular instance of the class it has to assign its delegate?