Accept Both UIButtons and UIViews as parameters for method - objective-c

How can I write a function that will take either a UIView or a UIButton as a parameter.
For example, let's say I wanted to round the corners the View/Button:
[self roundCorners:x]
How could I make that accept both a UIButton object or a UIView object as x?
My first thoughts were use id or NSObject, but I don't think that is correct:
-(void)roundCorners:(id)objectToRound;

UIButton is a subclass of UIView, so your method will take either one if you declare it this way:
- (void)roundCorners:(UIView *)objectToRound;

Short answer:
if ([view isKindOfClass:[UIButton class]]) {
UIButton *button = (UIButton *) view;
// do something funky with button
} else {
// do something funky with view
}
Detailed answer:
First of all, it is usually best to have a specific method to handle each class of object. This is so you can have the compiler resolve potentially passing the wrong class to a method. There are however times when this is not appropriate or possible.
UIView and UIButton are both ultimately a descendant of NSObject.
NSObject has a method isKindOfClass: which you can use to determine what you are dealing with. I have taken it a step further by defining a few helper macros that simplify these sorts of tasks, which are sometimes quite laborious.
If you have a look at my answer to this question... you will see source code for, and an example of using these macros. I often use one of these macros (asClass), which combines a isKindOfClass: test linked to a type cast to the class being tested for.
You can code such a thing long hand, however I find it creates more readable code to use the asClass macro. Put simply asClass(myView,UIButton) will either resolve to nil or to a type cast reference to myView as a UIButton - if it is not a UIButton, it will be resolved to nil.
I have modified a snippet from that post to suit your question:
- (void)myMethod:(UIView*)view{
UIButton *button = asClass(view, UIButton);
if (button) {
// do something funky with button
} else {
// do something funky with view
}
}
To code this long hand, (without macros) it would look approximately like this:
- (void)myMethod:(UIView*)view{
if ([view isKindOfClass:[UIButton class]]) {
UIButton *button = (UIButton *) view;
// do something funky with button
} else {
// do something funky with view
}
}
As you can see, it's not too much extra code to do it long hand, and if you are not comfortable using macros for things like this (some people aren't) then you may choose to stick with the second example.
If you are comfortable with macros, and can see the merits of the approach I have taken, consider this third way of doing it:
- (void)myMethod:(UIView*)view{
UIButton *button;
if ( (button = asClass(view, UIButton)) ) {
// do something funky with button
} else {
// do something funky with view
}
}

The way you do it should work just fine, id will accept almost anything.

Related

menuWillOpen: and menuDidClose: not invoked for NSMenuDelegate

[Edit] as Willeke helpfully points out it's menuDidClose: NOT menuWillClose:. My code actually had that part right. Correcting the post in case someone else finds this researching a similar problem.
I'm sure this is just a Cocoa newbie problem but I've wracked my brain on it for hours. I've read the NSMenu and NSMenuDelegate docs a few times trying to figure out what I'm missing but it looks straight forward.
I have a window controller for a preferences window with a toolbar and three views. The window controller is declared as NSMenuDelegate.
#interface PrefsController : NSWindowController <NSMenuDelegate, NSWindowDelegate, NSOpenSavePanelDelegate>
This issue is a NSPopUpButton on the first view. The menu associated with popupbutton works fine. I can modify, etc. the menu via the associated IBOutlet variable. It's bound to Shared User Defaults Controller for selected value and that works fine.
But the menuWillOpen: and menuDidClose: methods are not invoked when the menu is accessed.
- (void)menuWillOpen:(NSMenu *)menu {
if (menu == myPopupButton.menu) {
[self updateMenuImages:NSMakeSize(32, 32)];
}
}
- (void)menuDidClose:(NSMenu *)menu {
if (menu == myPopupButton.menu) {
[self updateMenuImages:NSMakeSize(16, 16)];
}
}
My apologies for what is almost certainly a dumb mistake on my part, but I'm stumped.
Menu delegates are not used that often, so Apple hasn't made them too easy to set up in Interface Builder. Instead, do this in awakeFromNib:
myPopupButton.menu.delegate = self;

How to go about customizing UIButton

I'm trying to customize my buttons by using a category but I don't want all my buttons in my project to use this category. Is there a way to let the button know it should/shouldn't use the category (Like some sort of bool isUsingCategory). If not, whats the best way to go about this? I would subclass it but I hear that subclasssing UIButton is a bad idea. Is there a way to exclude categories from certain objects? Thanks!
EDIT: What I have right now that's working is in my controller running a special method to draw the buttons but I don't think thats follows MVC very well.
You just need to inherit from the UIButton (for example MyButton) and add one property something like this:
#property (nonatomic, assign) BOOL useCategoryBehavior;
In the category of MyButton use this property before do something in overriden methods. Like this:
- (void)viewDidLoad {
[super viewDidLoad];
if (useCategoryBehavior) {
// do smth here
}
}

How to get a reference to the view controller of a superview?

Is there a way to get a reference to the view controller of my superview?
There were several instances that I needed this on the past couple of months, but didn't know how to do it. I mean, if I have a custom button on a custom cell, and I wish to get a reference of the table view controller that controls the cell I`m currently in, is there a code snippet for that? Or is it something that I should just solve it by using better design patterns?
Thanks!
Your button should preferably not know about its superviews view controller.
However, if your button really needs to message objects that it shouldn't know the details about, you can use delegation to send the messages you want to the buttons delegate.
Create a MyButtonDelegate protocol and define the methods that everyone that conforms to that protocol need to implement (the callback). You can have optional methods as well.
Then add a property on the button #property (weak) id<MyButtonDelegate> so that any class of any kind can be set as the delegate as long as it conforms to your protocol.
Now the view controller can implement the MyButtonDelegate protocol and set itself as the delegate. The parts of the code that require knowledge about the view controller should be implemented in the delegate method (or methods).
The view can now send the protocol messages to its delegate (without knowing who or what it is) and the delegate can to the appropriate thing for that button. This way the same button could be reused because it doesn't depend on where it is used.
When I asked this question I was thinking of, in a situation where I have custom cells with buttons on them, how can the TableViewController know which cell's button was tapped.
More recently, reading the book "iOS Recipes", I got the solution:
-(IBAction)cellButtonTapped:(id)sender
{
NSLog(#"%s", __FUNCTION__);
UIButton *button = sender;
//Convert the tapped point to the tableView coordinate system
CGPoint correctedPoint = [button convertPoint:button.bounds.origin toView:self.tableView];
//Get the cell at that point
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:correctedPoint];
NSLog(#"Button tapped in row %d", indexPath.row);
}
Another solution, a bit more fragile (though simpler) would be:
- (IBAction)cellButtonTapped:(id)sender
{
// Go get the enclosing cell manually
UITableViewCell *parentCell = [[sender superview] superview];
NSIndexPath *pathForButton = [self.tableView indexPathForCell:parentCell];
}
And the most reusable one would be to add this method to a category of UITableView
- (NSIndexPath *)prp_indexPathForRowContainingView:(UIView *)view
{
CGPoint correctedPoint = [view convertPoint:view.bounds.origin toView:self];
return [self indexPathForRowAtPoint:correctedPoint];
}
And then, on your UITableViewController class, just use this:
- (IBAction)cellButtonTapped:(id)sender
{
NSIndexPath *pathForButton = [self.tableView indexPathForRowContainingView:sender];
}
If you know which class is the superview of your view controller, you can just iterate through the subviews array and typecheck for your superclass.
eg.
UIView *view;
for(tempView in self.subviews) {
if([tempView isKindOfClass:[SuperViewController class] ])
{
// you got the reference, do waht you want
}
}

Cocoa NSView subview blocking drag/drop

I have an NSView subclass which registers for drag files in init method like this:
[self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
The drag drop works perfectly fine, but if I add a subview to this view with the exact same frame, it doesn't work any more. My guess is that the subview is block the drag event to go to super view. Can can I avoid that? Thanks
Also, I know I am asking two questions, but I don't want to create a new topic just for this: When I am dragging, my cursor doesn't change to the "+" sign like with other drags, how can I do that?
Thanks again.
UPDATE:
Here's the how I have it set up in my IB:
The DrawView is the custom class I was talking about that registered for draggedtypes. And the Image view simply is a subview, I dragged an image from the media section...
If it helps, here's my relevant code for DragView:
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
return NSDragOperationCopy;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
NSPasteboard *pboard;
pboard = [sender draggingPasteboard];
NSArray *list = [pboard propertyListForType:NSFilenamesPboardType];
if ([list count] == 1) {
BOOL isDirectory = NO;
NSString *fileName = [list objectAtIndex:0];
[[NSFileManager defaultManager] fileExistsAtPath:fileName
isDirectory: &isDirectory];
if (isDirectory) {
NSLog(#"AHH YEA");
} else {
NSLog(#"NOO");
}
}
return YES;
}
The answer to the second part of your question is this:
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender{
return NSDragOperationCopy;
}
if you return NSDragOperationCopy, you get the mouse badge for a copy operation. (You can and should, of course, not just return NSDragOperationCopy unconditionally; check the objects on the pasteboard to see if you can accept them.)
I'm not sure what the answer to the first part of your question is, because I'm unable to recreate the subview blocking effect.
Okay, the answer is unfortunately that you can't. The image you dragged is contained in an NSImageView, and NSImageViews accept drag events themselves, so it's grabbing the drag event and not doing anything with it. If your subview was a custom class, you could either a) not implement drag and drop, in which case the drags would be passed through; b) implement drag and drop to accept drags for the subview. In this case, you're using a class over which you don't have any control. If all you want it to do is display an image, you should be able to make another NSView subclass that does nothing but draw the image in drawRect:
As mentioned in the comments, NSImageViews have their own drag and drop enabled by default (used for accepting images that are dragged onto the NSImageView). If you don't want to use this behavior and instead want to use the super view's drag and drop behavior, you'll want to unregister the dragging behavior in the NSImageView.
Objc:
[imageView unregisterDraggedTypes];
Swift:
imageView.unregisterDraggedTypes()
(in case anyone else stumbled across this question first and not the linked one in the comments)

Call super in overridden class method [duplicate]

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