I'm trying to subclass UIColor, and I can't seem to figure out what's wrong.
In my PColor.h
#import <Foundation/Foundation.h>
#interface PColor : UIColor {
BOOL isAvailable;
int colorId;
}
#property (nonatomic, assign) BOOL isAvailable;
#property (nonatomic, assign) int colorId;
#end
...and in my PColor.m
#import "PColor.h"
#implementation PColor
#synthesize isAvailable;
#synthesize colorId;
#end
Upon instantiating a PColor object, I get:
//warning: incompatible Objective-C types initializing 'struct UIColor *', expected 'struct PColor *'
PColor *pcolor = [[PColor alloc] initWithHue:1 saturation:0 brightness:0 alpha:1];
Am I missing something? Thanks in advance.
UIColor is a class cluster use associative references in a category to add properties! All of the custom init methods on UIColor return a UIColor* not an id so you can not easily subclass UIColor nor should you try.
UIColor+PCOLOR.h
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#interface UIColor(PCOLOR)
//Properties prefixed to try and avoid future conflicts
#property (nonatomic, assign) BOOL pIsAvailable;
#property (nonatomic, assign) int pColorId;
#end
UIColor+PCOLOR.h
#import "UIColor+PCOLOR.h"
#implementation UIColor(PCOLOR)
static char PCOLOR_ISAVAILABLE_KEY;
static char PCOLOR_COLORID_KEY;
#dynamic pIsAvailable, pColorId;
-(void)setPIsAvailable:(BOOL)pIsAvailable
{
objc_setAssociatedObject(self, &PCOLOR_ISAVAILABLE_KEY, [NSNumber numberWithBool:pIsAvailable], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(BOOL)pIsAvailable
{
return [(NSNumber*)objc_getAssociatedObject(self, &PCOLOR_ISAVAILABLE_KEY) boolValue];
}
-(void)setPColorId:(int)pColorId
{
objc_setAssociatedObject(self, &PCOLOR_COLORID_KEY, [NSNumber numberWithInt:pColorId], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(int)pColorId
{
return [(NSNumber*)objc_getAssociatedObject(self, &PCOLOR_COLORID_KEY) intValue];
}
#end
USAGE
UIColor *pcolor = [[UIColor alloc] initWithHue:1 saturation:0 brightness:0 alpha:1];
pcolor.pColorId = 2352;
pcolor.pIsAvailable = YES;
NSLog(#"\nClass: %#\nColor ID: %d\nIs Availabled: %#",
NSStringFromClass([pcolor class]),
pcolor.pColorId,
pcolor.pIsAvailable ? #"YES" : #"NO");
[pcolor release];
From UIColor Class Reference:
Most developers should have no need to subclass UIColor. The only time doing so might be necessary is if you require support for additional colorspaces or color models.
You should use Category. For example:
#interface UIColor (PColor)
- (BOOL) isAvailable;
- (int) colorId;
#end
In the implementation file:
#implementation UIColor (PColor)
- (BOOL)isAvailable {
// do what you want to do
// return your BOOL
}
- (int)colorId {
// do what you want to do
// return id of color
}
#end
Because UIColor alloc might not do what you expect it to do: allocating an instance of UIColor. It may be kind of a factory method, which looks first what colors have already been used or belong to a standard set of colors and give it back instead of creating a new instance. In which case you will be getting UIColor instead of PColors and what means that inheriting UIColor was not a good idea.
Prefer composition over inheritance - embed UIColor within a PColor. Or use a category on UIColor (you can't have new instance variables in this case).
The init methods of UIColor return a UIColor* rather than id as with most classes, so you would have to assign it to a UIColor rather than your subclass to avoid the warning.
Related
I'm relatively new to ARC. I'm making an UIView subclass, that will have two labels (title and subtitle). I don't want to publicly expose the labels as properties, only their text.
I'm currently using this:
#interface MyView : UIView
#property (nonatomic, strong) NSString *title;
#property (nonatomic, strong) NSString *subtitle;
#end
#implementation MyView
{
UILabel *_titleLabel;
UILabel *_subtitleLabel;
}
- (void)setTitle:(NSString *)title
{
[_titleLabel setText:title];
}
- (NSString *)title
{
return [_titleLabel text];
}
- (void)setSubtitle:(NSString *)subtitle
{
[_subtitleLabel setText:title];
}
- (NSString *)subtitle
{
return [_subtitleLabel text];
}
#end
Are my two #properties correctly declared? Should I use the strong, weak or any other qualifier? And why?
If you are going to work with setter / getter, I think the appropiate tag would be the readwrite. strong weak retain etc apply when the property is the setter/getter for an instance variable.
In Cocoa Programming for Mac OS X (Hillegass), there is a class in chapter 19:
#interface BigLetterView : NSView {
NSColor *bgColor;
}
#property (strong) NSColor *bgColor;
#end
The accessor is defined like this:
- (void)setBgColor:(NSColor *)c {
bgColor = c;
[self setNeedsDisplay:YES]; }
This looked correct to me, but it creates an infinite loop: bgColor = c calls setBgColor:c
Is this code correct ?
How to redefine a setter? What do I need to compile this code ?
In modern Xcode/llvm you can clean up your code a bit.
Namely you dont need to declare neither a member variable as backing variable, nor a synthesize statement.
If you dont declase synthesize your self, a property foo will have the backing variable _foo
So this code should work
#interface BigLetterView : NSView
#property (nonatomic, strong) NSColor *bgColor;
#end
#implementation BigLetterView
#synthesize bgColor = _bgColor;
- (void)setBgColor:(NSColor *)c
{
_bgColor = c;
[self setNeedsDisplay:YES];
}
#end
Do it this way which looks cleaner and better to understand.
#interface BigLetterView : NSView
#property (strong) NSColor *bgColor;
#end
#implementation BigLetterView
- (void)setBgColor:(NSColor *)c {
_bgColor = c;
[self setNeedsDisplay:YES];
}
Also, make a simple rule to always use the property with self in any other places you use it. It is just a better rule for making you understand things clearly.
The code is probably the best way to see what I am trying to do:
AcInfo.h:
#interface AcInfo : NSManagedObject {
#private
}
#property (nonatomic, retain) NSString *registrationNumber;
#end
AcInfo.m:
#implementation AcInfo
#dynamic registrationNumber;
#end
AnotherClass.h:
#interface AnotherClass : NSObject {
}
#property (nonatomic, retain) AcInfo *detailItem;
#property (nonatomic, retain) IBOutlet UITextField *registrationNumberTextField;
- (void)setDetailItemValueFromUIElement:(id *)uiElement forAcInfoTarget:(id *)acInfoTarget;
#end
AnotherClass.m:
#import "AcInfo.h"
#implementation AnotherClass
#synthesize detailItem, registrationNumberTextField;
- (void)viewDidLoad
{
[super viewDidLoad];
registrationNumberTextField.text = #"Test";
// I expect this to set detailItem.registrationNumber to the value of
// registrationNumberTextField.text (Test) but it doesn't change anything!
setDetailItemValueFromUIElement:registrationNumberTextField forAcInfoTarget:detailItem.registrationNumber;
}
- (void)setDetailItemValueFromUIElement:(id *)uiElement forAcInfoTarget:(id *)acInfoTarget
{
if ([(id)uiElement isKindOfClass:[UITextField class]]) {
// This doesn't do anything when it returns!
(NSString *)acInfoTarget = (UITextField *)uiElement.text
return;
}
}
#end
In short, I want acInfoTarget to call the getter [detailObject registrationNumber] and the setter [detailObject setRegistrationNumber] in the setDetailItemValueFromUIElement: function...
You can set or read properties by name using
// setter
NSString *propertyName = #"myProperty";
[object setValue:newValue forKey:propertyName];
// getter
id value = [object valueForKey:propertyName];
This is slower than using the normal dot notation, though, and it's frequently (though not always) a sign of poorly-designed code.
Also note that id is a pointer type, so you probably don't actually mean "(id*)".
Your code wants to look something like this, I think:
- (void)setDetailItemValueFromUIElement:(id)uiElement forAcInfoTarget:(NSString*)acInfoTarget {
if ([(id)uiElement isKindOfClass:[UITextField class]]) {
NSString *newValue = ((UITextField*)uiElement).text;
[self.detailItem setValue:newValue forKey:acInfoTarget];
}
}
Properties are just syntax sugar for a couple of accessor methods. They are not, in essence, variables so you shouldn't treat them as such. If you want to affect a property, then what you wanting to do is call a method. So you should pass a id and selector parameter and not pointer to a variable type.
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
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
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)
}
}
}
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.
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
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
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]];
}
}
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.
I keep reading that dot syntax is possible but I keep getting errors that the struct does not contain members I am referencing. Perhaps its not the dot syntax so I have included details of what I am doing in hopes of a solution:
// MobRec.h - used as the objects in the MobInfo array
#import <Foundation/Foundation.h>
#interface MobRec : NSObject {
#public NSString *mName;
#public int mSpeed;
}
#property (nonatomic, retain) NSString *mName;
#property (nonatomic) int mSpeed;
// MobDefs.h - array of MobRecords
#interface Mobdefs : NSObject {
#public NSMutableArray *mobInfo;
}
#property(assign) NSMutableArray *mobInfo; // is this the right property?
-(void) initMobTable;
#end
// MobDefs.m
#import "Mobdefs.h"
#import "Mobrec.h"
#implementation Mobdefs
#synthesize mobInfo;
-(void) initMobTable
{
// if I use traditional method I get may not respond
[mobInfo objectAtIndex:0 setmName: #"doug"];
// if I use dot syntax I get struct has no member named mName
mobInfo[1].MName = #"eric";
}
// main.h
MobDefs *mobdef;
// main.m
mobdef = [[Mobdefs alloc] init];
[mobdef initMobTable];
although both methods should work I get erros on both. What am I doing wrong? My best thoughts have been that I am using the wrong #property but I think I have tried all. I am performing alloc in main. Ideally I would like to for this use dot syntax and cant see why its not allowing it.
A couple of things: (edit: original point #1 removed due to error)
Although the dot syntax is supported, the array index syntax for NSArray is not. Thus, your call to mobInfo[1] will not be the same as [mobInfo objectAtIndex:1]; Instead, mobInfo will be treated as a simple C-style array, and that call would be almost guaranteed to result in a crash.
You should not define variables in your header file as you do in main.h. The line MobDefs *mobdef; belongs somewhere in main.m.
edit: Here is how it should look:
MobRec.h
#interface MobRec : NSObject {
NSString *mName;
int mSpeed;
}
#property (nonatomic, retain) NSString *mName;
#property (nonatomic) int mSpeed;
MobRec.m
#implementation MobRec
#synthesize mName;
#synthesize mSpeed;
#end
MobDefs.h
#interface MobDefs : NSObject {
NSMutableArray *mobInfo;
}
#property(assign) NSMutableArray *mobInfo;
-(void) initMobTable;
#end
MobDefs.m
#import "MobDefs.h"
#import "MobRec.h"
#implementation MobDefs
#synthesize mobInfo;
-(void) initMobTable
{
// option 1:
[(MobRec*)[mobInfo objectAtIndex:0] setMName:#"doug"];
// option 2:
(MobRec*)[mobInfo objectAtIndex:0].mName = #"eric";
// option 3:
MobRec *mobRec = [mobInfo objectAtIndex:0];
mobRec.mName = #"eric";
}
main.m
MobDef *mobdef = [[MobDefs alloc] init];
[mobdef initMobTable];
...
[mobdef release]; // don't forget!
You need to either cast the object returned by -objectAtIndex:, or use a method call on it:
[[mobInfo objectAtIndex: 0] setMName: #"doug"];
or
((Mobrec *) [mobInfo objectAtIndex: 0]).MName = #"doug";
[mobInfo objectAtIndex:0 setmName: #"doug"];
There is no objectAtIndex:setmName method, so you're going to have to explain what you think this is even supposed to do.
mobInfo[1].MName = #"eric";
Use objectAtIndex to look something up in an NSArray object.