35

I'd like to subclass UIButton to add some properties that i need (not methods... only properties).

Here the code of my subclass:

//.h-----------------------
@interface MyButton : UIButton{
    MyPropertyType *property;
}

@property (nonatomic,retain) MyPropertyType *property;
@end

//.m--------------------------
@implementation MyButton
@synthesize property;

@end

And here how I use the class:

MyButton *btn = ((MytButton *)[MyButton buttonWithType:UIButtonTypeRoundedRect]);
btn.property = SomeDataForTheProperty;

From where i obtain this error :

 -[UIRoundedRectButton setProperty:]: unrecognized selector sent to instance 0x593e920

Thus, from ButtonWithType i obtain a UIRoundedRectButton and (Mybutton *) can't cast it... What i have to do to obtain a MyButton object ? is -init the unique solution ?

Thank you!

Bhavin Ramani
  • 3,221
  • 5
  • 30
  • 41
MatterGoal
  • 16,038
  • 19
  • 109
  • 186
  • I can confirm that with init method it works, but i obtain a UIButtonTypeCustom ... not a roundRect – MatterGoal Mar 31 '11 at 13:12
  • subclassing the button is working for me in iOS6 and iOS7, I wonder if this was broken in earlier OS'es. – Skotch Dec 04 '13 at 19:28

3 Answers3

88

Try using a category with Associative References instead. It is much cleaner and will work on all instances of UIButton.

UIButton+Property.h

#import <Foundation/Foundation.h>

@interface UIButton(Property)

@property (nonatomic, retain) NSObject *property;

@end

UIButton+Property.m

#import "UIButton+Property.h"
#import <objc/runtime.h>

@implementation UIButton(Property)

static char UIB_PROPERTY_KEY;

@dynamic property;

-(void)setProperty:(NSObject *)property
{
    objc_setAssociatedObject(self, &UIB_PROPERTY_KEY, property, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(NSObject*)property
{
    return (NSObject*)objc_getAssociatedObject(self, &UIB_PROPERTY_KEY);
}

@end

//Example usage

#import "UIButton+Property.h"

...

UIButton *button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button1.property = @"HELLO";
NSLog(@"Property %@", button1.property);
button1.property = nil;
NSLog(@"Property %@", button1.property);
Cœur
  • 37,241
  • 25
  • 195
  • 267
Joe
  • 56,979
  • 9
  • 128
  • 135
  • I used `NSObject` but the type can be any type you define including `MyPropertyType` also `UIButton(Property)` is really generic consider renaming property to something more useful. – Joe Mar 31 '11 at 13:27
  • This's a clean way to do exactly what i'm asking for :) thank you – MatterGoal Apr 04 '11 at 09:04
  • I didn't use your answer, but I learned something new (objc_setAssociatedObject). So thank you. – Chris Brandsma Jun 21 '11 at 18:43
  • excellent - this works great for using UIButtonTypeContactAdd at the end of a row and passing objects. (when .tag isn't sufficient). – roocell Dec 25 '11 at 14:43
  • where did you initialize static char UIB_PROPERTY_KEY;? – Anonymous White Oct 28 '12 at 12:01
  • 1
    You do not need to initialize it, the address is the key. – Joe Oct 28 '12 at 12:31
  • all we care is that it's different. Also can we combine this with sub classing for truly elegant solution. – Anonymous White Oct 28 '12 at 12:54
  • Looks like this thing won't work with subclassing isn't it. For all IOS know the object is of type UIButton and unless you add the category to the class, which would defeat the purpose of encapsulating, it'll still complain. – Anonymous White Oct 28 '12 at 13:11
  • This solution worked great for me, originally I was going to subclass, but this method is soo much cleaner. Cheers!!! – AdamM Nov 13 '12 at 16:37
  • 2
    I dont really think this is a clean solution... This is a hacky solution instead. – João Nunes Feb 11 '13 at 13:30
  • 1
    @JoãoNunes Care to explain what is ["hacky"](http://en.wikipedia.org/wiki/Hack_(computer_science)#In_computer_science) about a [well documented solution](http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html#//apple_ref/doc/uid/TP40011210-CH6-SW1)? – Joe Feb 11 '13 at 14:25
  • using objc low-level methods is not pretty at all and looks hacky, subclassing is much more objective programming and less confusing. – João Nunes Feb 13 '13 at 08:07
  • 2
    In the case of UIButton I think this is a much better solution than subclassing, as calling buttonWithType: on a subclass will not return an object with the inherited methods. – David John Smith Jul 01 '13 at 00:49
  • ofc... you have to subclass the initialiser too! To create a different object otherwise you are just creating a UIButton. – João Nunes Dec 19 '13 at 09:47
  • Is it allowed - Would an App get approved using this? – Luke Jan 21 '14 at 04:22
  • For future users: dont miss #import to avoid getting into this trouble http://stackoverflow.com/questions/30025593/ios-objective-c-how-can-i-resolve-this-error-implicit-declaration-of-functi – jeet.chanchawat Dec 23 '15 at 12:54
12

You need to do:

MyButton *btn = [[MyButton alloc] init];

To create your button. The buttonWithType:UIButtonTypeRoundedRect only creates UIButton objects.

=== edit ===

If you wish to use a RoundedRect button; then I would suggest the following. Basically, we just create a UIView with whatever properties we want and add the desired button to that view.


.h

@interface MyButton : UIView
{
    int property;
}

@property int property;
@end

.m

@implementation MyButton
@synthesize property;

- (id)initWithFrame:(CGRect)_frame
{
    self = [super initWithFrame:_frame];
    if (self)
    {
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        btn.frame = self.bounds;
        [self addSubview:btn];
    }
    return self;
}

@end

Usage:

MyButton *btn = [[MyButton alloc] initWithFrame:CGRectMake(0, 0, 200, 20)];
btn.property = 42;

[self.view addSubview:btn];

Community
  • 1
  • 1
Wex
  • 4,434
  • 3
  • 33
  • 47
  • Doesn't this method limit you to using buttons of type UIButtonTypeCustom? – Joe Mar 31 '11 at 15:25
  • 3
    Now he has no access to any button methods unless he exposes btn through a property. – Joe Mar 31 '11 at 16:57
  • @Joe: okay, it could be promoted to a property, too. Or you could hide some of the `UIButton` functionality. Great Facade, Wex. – Dan Rosenstark Feb 09 '12 at 20:57
2

I have a simple scheme that only involves a few library methods, no boilerplate, and just 3 lines of code for each property you want to add. There are two example properties added below: startPoint and tileState. For illustrative purposes here are the lines you'd need to add for a property like tileState:

//@property (assign, nonatomic) SCZTileState tileState; // tileState line 1 
//@property (assign, nonatomic) SCZTileState tileState; // tileState line 2 
//@dynamic tileState;                                   // tileState line 3

There's more details in my blog post describing how this works

UIButton+SCZButton.h

#import <UIKit/UIKit.h>

@interface UIButton (SCZButton)
@property (readwrite, nonatomic) id assocData;
@end

UIButton+SCZButton.m

//  UIButton+SCZButton.m
//  Copyright (c) 2013 Ooghamist LLC. All rights reserved.

#import "UIButton+SCZButton.h"
#import <objc/runtime.h>

@implementation UIButton (SCZButton)
- (id)assocData {
    id data = objc_getAssociatedObject(self, "SCZButtonData");
    return data;
}
- (void)setAssocData:(id)data {
    objc_setAssociatedObject(self, "SCZButtonData", data,  
                             OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

OOGTotallyTile.h

//  UIButton+OOGTotallyTile.m
//  Copyright (c) 2013 Ooghamist LLC. All rights reserved.
#import <UIKit/UIKit.h>
#import "UIButton+SCZButton.h"
#define kPointLabelTag 837459

typedef enum {
    SCZTileStatePlaced,
    SCZTileStateDropping,
    SCZTileStateDropped
} SCZTileState;

@interface SCZButtonData : NSObject
@property (assign, nonatomic) CGPoint startPoint;
@property (assign, nonatomic) SCZTileState tileState;   // tileState line 1
@end

@interface UIButton (OOGTotallyTile)
@property (readonly, nonatomic) SCZButtonData *buttonData;
@property (assign, nonatomic) CGPoint startPoint;
@property (assign, nonatomic) SCZTileState tileState;  // tileState line 2
@end

OOGTotallyTile.m

//  UIButton+OOGTotallyTile.m
//  Copyright (c) 2013 Ooghamist LLC. All rights reserved.

#import "OOGTotallyTile.h"

@implementation SCZButtonData
@end

@implementation UIButton (OOGTotallyTile)
@dynamic startPoint;
@dynamic tileState; // tileState line 3

- (SCZButtonData*)buttonData {
    if ( ! self.assocData) {
        self.assocData = [[SCZButtonData alloc] init];
    }
    return self.assocData;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
    id forwardingTarget = [super forwardingTargetForSelector:aSelector];
    if ( ! forwardingTarget) {
        return [self buttonData];
    }
    return forwardingTarget;
}
@end
StCredZero
  • 464
  • 3
  • 13