14

Ok, so I have this, but it wont work:

@interface UILabel (touches)

@property (nonatomic) BOOL isMethodStep;

@end


@implementation UILabel (touches)

-(BOOL)isMethodStep {
    return self.isMethodStep;
}

-(void)setIsMethodStep:(BOOL)boolean {
    self.isMethodStep = boolean;
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if(self.isMethodStep){
        // set all labels to normal font:
        UIFont *toSet = (self.font == [UIFont fontWithName:@"Helvetica" size:16]) ? [UIFont fontWithName:@"Helvetica-Bold" size:16] : [UIFont fontWithName:@"Helvetica" size:16];

        id superView = self.superview;
        for(id theView in [(UIView *)superView subviews])
            if([theView isKindOfClass:[UILabel class]])
                [(UILabel *)theView setFont:[UIFont fontWithName:@"Helvetica" size:16]];

        self.font = toSet;
    }
}

@end

If I take out the getter and setter methods then it doesn't work it tells me I need to create some getter and setter methods (or use @synthesize - but putting @synthesize in the @implementation throws an error too). But with the getter and setter methods I get an EXC_BAD_ACCESS and a crash. Any ideas? Thanks

Tom

vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
Thomas Clayson
  • 29,657
  • 26
  • 147
  • 224
  • This guy 'Dave DeLong' posted an answer that you should check out... it's probably the "correct" way to do what you want... :) [here's the link...](http://stackoverflow.com/questions/4146183/instance-variables-for-objective-c-categories) – Alex Zak Jul 04 '11 at 15:54
  • 1
    Associative reference is solution... find nice tutorial here http://www.techpaa.com/2012/04/adding-properties-to-categories-and.html – ShivaPrasad Apr 17 '12 at 11:47
  • 1
    Also, your `isMethodStep` getter is an infinitely recursive function. That's why you're getting an EXC_BAD_ACCESS crash. `self.isMethodStep` is equivalent to `[self isMethodStep]`. – Drew H Oct 06 '14 at 18:43

7 Answers7

40

It is not possible to add members and properties to an existing class via a category — only methods.

https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/Category.html

One possible workaround is to write "setter/getter-like" methods, that uses a singleton to save the variables, that would had been the member.

-(void)setMember:(MyObject *)someObject
{
    NSMutableDictionary *dict = [MySingleton sharedRegistry];
    [dict setObject:someObject forKey:self];
}

-(MyObject *)member
{
    NSMutableDictionary *dict = [MySingleton sharedRegistry];
    return [dict objectforKey:self];
}

or — of course — write a custom class, that inherits from UILabel


Note that nowadays an associated object can be injected during runtime. The Objective C Programming Language: Associative References

Cœur
  • 37,241
  • 25
  • 195
  • 267
vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
4

Checked all answers and did not find the most common solution:

#import <objc/runtime.h>

static void const *key;

@interface ClassName (CategoryName)
@property (nonatomic) BOOL myProperty;
@end

@implementation ClassName (CategoryName)
- (BOOL)myProperty {
    return [objc_getAssociatedObject(self, key) boolValue];
}

- (void)setMyProperty:(BOOL)value {
    objc_setAssociatedObject(self, key, @(value), OBJC_ASSOCIATION_RETAIN);
}
@end

swift:

private struct AssociatedKeys {
    static var keyName = "keyName"
}

extension Foo {
    var bar: Any! {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.keyName)
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.keyName , newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}
Kirow
  • 1,077
  • 12
  • 25
1

There is actually a way, which may not be ideal, but does work.
For it to work, you will need to create a category for a class X and can only be used on subclasses of the same X (e.g. category UIView (Background) can be used with class MyView : UIView, but not directly with UIView)

// UIView+Background.h

@interface UIView (Background)

@property (strong, nonatomic) NSString *hexColor;

- (void)someMethodThatUsesHexColor;

@end

// UIView+Background.h

@implementation UIView (Background)

@dynamic hexColor; // Must be declared as dynamic

- (void)someMethodThatUsesHexColor {
    NSLog(@"Color %@", self.hexColor);
}

@end

Then

// MyView.m

#import "UIView+Background.h"

@interface MyView : UIView

@property (strong, nonatomic) NSString *hexColor;

@end

@implementation MyView ()

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setHexColor:@"#BABACA"];
    [self someMethodThatUsesHexColor];
}

@end

Using this method, you will need to "redeclare" your properties, but after that, you can do all of its manipulation inside your category.

Guilherme
  • 7,839
  • 9
  • 56
  • 99
1

You could inject an associated object during runtime.

#import <objc/runtime.h>

@interface UIView (Private)

@property (nonatomic, assign) CGPoint initialTouchPoint;
@property (nonatomic, strong) UIWindow *alertWindow;

@end

@implementation UIView (Private)

@dynamic initialTouchPoint, alertWindow;

- (CGPoint)initialTouchPoint {
    return CGPointFromString(objc_getAssociatedObject(self, @selector(initialTouchPoint)));
}

- (void)setInitialTouchPoint:(CGPoint)initialTouchPoint {
    objc_setAssociatedObject(self, @selector(initialTouchPoint), NSStringFromCGPoint(initialTouchPoint), OBJC_ASSOCIATION_RETAIN);
}

- (void)setAlertWindow:(UIWindow *)alertWindow {
    objc_setAssociatedObject(self, @selector(alertWindow), alertWindow, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIWindow *)alertWindow {
    return objc_getAssociatedObject(self, @selector(alertWindow));
}

@end
Lal Krishna
  • 15,485
  • 6
  • 64
  • 84
0

EDIT: Warning: This property would have a unique value for all the instances of the class.

This worked for me, but only because I had only one instance of this class in my app.

#import <AVFoundation/AVFoundation.h>

@interface AVAudioPlayer (AstroAVAudioPlayer)

@property (nonatomic) BOOL redPilot;

@end


#import "AVAudioPlayer+AstroAVAudioPlayer.h"

@implementation AVAudioPlayer (AstroAVAudioPlayer)

BOOL _redPilot;

-(void) setRedPilot:(BOOL)redPilot
{
    _redPilot = redPilot;
}

-(BOOL) redPilot
{
    return _redPilot;
}

@end
Maxim Chetrusca
  • 3,262
  • 1
  • 32
  • 28
  • 2
    This answer should be flagged as incorrect. The implementation above only allocates one property called _redPilot for all instances of AVAudioPlayer. In other words, if you have three instances of AVAudioPlayer, then avAudioPlayer1.redPilot == avAudioPlayer2.redPilot == avAudioPlayer3.redPilot always. – noobular Jun 27 '13 at 21:26
  • 1
    @nobular, You're right, Sir. Indeed in my case I had only one instance of AVAudioPlayer, so I wasn't caring much about that. I answered this here anyway, because I thought this might be helpful for someone, for example someone like me. I would edit the answer so it warns about the above-mentioned issue. – Maxim Chetrusca Jun 28 '13 at 08:38
  • Here's a technique that I've used in the past for solving the lack of properties on categories: http://www.techpaa.com/2012/04/adding-properties-to-categories-and.html – noobular Aug 04 '13 at 07:00
  • 1
    In my case, this is a perfect solution. I'm trying to add a NSDateFormatter to the category of NSDate (maybe I'm crazy) to have only one instance of the formatter in all of my NSDate objects. What do you think about it? – Floydian Sep 04 '13 at 15:01
  • @Floydian, you know, there is a saying: "It's not a bug. It's a feature." :) So if you want such a feature, go for it, if it does the right job for you. – Maxim Chetrusca Sep 05 '13 at 16:27
0

A solution that I found to this was to just give each object that you want flagged a unique tag.

I made a UILabel category to add custom fonts to all my labels but on some i wanted them to be bold so i did this ->

- (void) layoutSubviews {
    [super layoutSubviews];
    [self addCustomFont];   
}

- (void) addCustomFont {
    if (self.tag == 22) {
        [self setFont:[UIFont fontWithName:SEGOE_BOLD size:self.font.pointSize]];
    }else{
        [self setFont:[UIFont fontWithName:SEGOE_LIGHT size:self.font.pointSize]];
    }
}
shokaveli
  • 478
  • 7
  • 14
0

It seems as if since Xcode 7 (7.0.1, 7A1001), properties are supported in categories. I noticed that Xcode generates categories now for Core Data subclasses.

For example, I got the files:

Location+CoreDataProperties.h

#import "Location.h"

NS_ASSUME_NONNULL_BEGIN

@interface Location (CoreDataProperties)

@property (nullable, nonatomic, retain) NSNumber *altitude;
@property (nullable, nonatomic, retain) NSNumber *latitude;
@property (nullable, nonatomic, retain) NSNumber *longitude;

@end

NS_ASSUME_NONNULL_END

Location+CoreDataProperties.m

#import "Location+CoreDataProperties.h"

@implementation Location (CoreDataProperties)

@dynamic altitude;
@dynamic latitude;
@dynamic longitude;

@end

So looks like properties in categories might work now. I haven't tested on non-Core Data classes.

What I've noticed is that they do include the category file back into the original class:

Location.h

@interface Location : NSManagedObject

@end

#import "Location+CoreDataProperties.h"

This allows the original class to edit the properties specified by the category.

mikeho
  • 6,790
  • 3
  • 34
  • 46