setting new properties in category interface/implementation

27,694

Solution 1

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

Solution 2

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)
        }
    }
}

Solution 3

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

Solution 4

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.

Share:
27,694
Thomas Clayson
Author by

Thomas Clayson

Updated on June 19, 2020

Comments

  • Thomas Clayson
    Thomas Clayson almost 4 years

    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