Objective-C sub-classing basics, how to add custom property; - iphone

I am having a issue subclassing MKPolygon.
I want to add a simple int tag property but I keep getting an instance of MKPolygon instead of my custom class, so calling setTag: causes an exception.
The problem is that MKPolygons are created using a class method: polygonWithCoordinates: count: and I dont know how to turn that into an instance of my class (which includes the tag property).
How would you go about adding a tag property to MKPolygon?
Thank you!

You should both use a category (as #Seva suggests) and objc_setAssociatedObject (as #hoha suggests).
#interface MKPolygon (TagExtensions)
#property (nonatomic) int tag;
#end
#implementation MKPolygon (TagExtensions)
static char tagKey;
- (void) setTag:(int)tag {
objc_setAssociatedObject( self, &tagKey, [NSNumber numberWithInt:tag], OBJC_ASSOCIATION_RETAIN );
}
- (int) tag {
return [objc_getAssociatedObject( self, &tagKey ) intValue];
}
#end
You may also want to look at Associative References section of the ObjC Guide, in addition to the API #hoha linked to.

Looks like developers of MKPolygon didn't make it inheritance friendly. If all you want is to add some tag to this instances you can
1) keep a map (NSDictionary or CFDictionary) from MKPolygon instance addresses to tags. This solution works well if all tags are required in the same class they are set.
2) use runtime to attach tag to polygons directly - objc_setAssociatedObject (Objective-C Runtime Reference)

I'm facing the same problem. A simple solution is to just use the Title property of the MKPolygon to save what you would save in Tag. At least in my case where I don't need an object reference but a simple number, it works

SpecialPolygon *polygon = [SpecialPolygon polygonWithCoordinates:count:];
[polygon setInt: 3];
The key is that by using the SpecialPolygon factory method instead of the MKPolygon one, you'll get the desired SpecialPolygon subclass.

Are you talking about MKPolygons created by your code, or elsewhere? If the former, just override the polygonWithStuff method. If the latter, consider a category over MKPolygon. Then all MKPolygons in your project will have a tag in them.

since it looks like the authors went out of their way to prevent you from subclassing (at least, that's one possible motivation for the public interface), consider using a form of composition:
http://en.wikipedia.org/wiki/Object_composition

Related

How to Use Objective-C Categories

When you implement a category of a class in a file, will all the instances of that class be of the category by default?
I'm new to Objective-C and I'm trying to make my uneditable UITextView non-selectable. I came across this answer using a category:
https://stackoverflow.com/a/8013538/1533240
Which has the following solution:
#implementation UITextView (DisableCopyPaste)
-(BOOL) canBecomeFirstResponder
{
return NO;
}
#end
I added the snippet to my code, but it doesn't seem to be working in that I can still select the text. My declaration of the UITextView is the usual:
titleLabel = [[UITextView alloc] initWithFrame:frame];
I tried changing the declaration to [DisableCopyPaste alloc] but that didn't seem to work.. haha.
Thanks!
You misunderstand the point of categories. Categories add methods to an existing class. They must never be used to override existing methods. Doing so is undefined behavior (technically only undefined in one case, but you can't predict that case, so you must assume it applies).
If you need to override methods, you must subclass, not use categories. See the top answer to the question you linked.
When you implement a category of a class in a file, will all the
instances of that class be of the category by default?
Yes. If you create a category, the methods in that category are added to the class. For example, if you create a category on NSString that returns the checksum of a string, you can use that method on any instance of NSString.
I added the snippet to my code, but it doesn't seem to be working in that I can still select the text.
Don't use categories to override existing methods.
For one thing, it's bad form. You're effectively changing the behavior of the class in a way that the author didn't expect. For another thing, you can't count on the override to work -- the order in which categories are added to classes isn't defined, so you never know if some other category might come along and replace the method that you tried to replace. It's simply not reliable. If you need to override methods, create a subclass instead.
What you need to do is to declare category in header .h file:
such as:
#interface UITextView (DisableCopyPaste)
-(BOOL) methodName
#end
then in .m define as
#implementation UITextView (DisableCopyPaste)
-(BOOL) methodName
{
return NO;
}
#end
You can do two thing,
You can write it in a class and import that to all classes you need this functionality.
Or write these lines eachs .h and .m (respectively) you need it.

What is the meaning of the console message: "snarfed from ivar layout..."?

I have a console message that appears to be triggered by apparently unrelated events.
The message states:
snarfed from ivar layout: [propertyName] = [constantString]
Where [propertyName] is the name of a property to which I set the value of a string constant [constantString].
What causes this message and what does it means?
I also ran into this issue recently. I was able to fix my specific issue, but I don't think that is exactly what the questioners are running into, since my issue was only being exposed in VoiceOver mode. As such, I'll offer thoughts on what I think is generally occurring and then I'll speak to my specific issue.
As for the general issue, I think that the Apple Framework is deciding to look through all of the ivars of a particular class in order to extract some information that it wants, but that is not provided by other parts of the UI element. This seems a little bizarre to me, but that is what I encountered.
So, to continue with the general case, and in answer to the initial question. If you're like me, then your property name is probably the same as your ivar. Try explicitly defining a getter method for that property. Then, set a breakpoint within that getter if you will be returning a non-nil value. Look at the stacktrace and that should tell you which piece of the apple frameworks is deciding to loop through your ivar layout in order to get the information it wants. (If you're not using the the same name for your property and ivar, then just define a property and getter with the ivar name and do the same thing with the breakpoint.)
My specific case was for a Custom Table Cell (like one of the commenters). In that cell,I had a property that was the same name as its ivar. I also had an explicitly defined getter for that property. I also referenced that custom table cell from the Nib file. So, it looked something like this:
class CustomTableViewCell:UITableViewCell
{
NSString *s ;
}
#property(nonatomic,retain) NSString *s ;
and in the implementation:
#synthesize s ;
-(NSString *)s
{
if( !s )
return self.reuseIdentifer ;
return s ;
}
I put a breakpoint in the return self.reuseIdentifier line,and that showed me a stacktrace from the Accessibility functions. The stacktrace showed my method being called by an Apple internal method that was looping through all of my ivars looking for something to use as the accessibilityLabel for my table cell. The name of the selector is '_accessibilityRetrieveTableViewIvarsText'.
To make matter worse, in my case, this was not just a debugger issue, it was messing up my Accessibility interface by using the wrong thing as the accessibilityLabel.
I came up with 3 fixes for my specific problem:
1) I added a value for the accessibilityLabel for the table cell inside the Nib. This satisfied the Apple framework to the point where it did not go searching through my ivars. This was not the solution I went with, however, because I did not want a static accessibility label.
2) I subclassed my CustomTableViewCell with an empty implementation and interface, and I used that as my Table cell class inside the Nib. This solved the problem because the Apple Framework looped through that class's ivars, of which there were none, and there weren't any values to 'snarf'. I did not use that solution either, but it might be the best one because it keeps Apple's frameworks from inspecting my ivars.
3) The solution I decided on was to make my ivar private and to define the property with a different name. That seems to be the standard way that a lot of folks use properties. This is what it looks like:
class CustomTableViewCell:UITableViewCell
{
#private
NSString *_s ;
}
#property(nonatomic,retain) NSString *s ;
and in the implementation:
#synthesize s = _s ;
-(NSString *)s
{
if( !_s )
return self.reuseIdentifer ;
return _s ;
}
This fixed the problem because nil is returned when Apple inspects the ivar, and, thus, nothing is 'snarfed'. I'm still not sure whether this or #2 is more appropriate.
"snarfed from ivar" basically autofills your accessibilityLabel. If you do that yourself, the message goes away, as there is no more need for sneeking into your UITableViewCell.
For future reference. The message is logged by the accessibility framework, which apparently looks through an UIView ivars for strings.
If you have a custom subclass you can define the custom attributes as specified in the following link:
Accessibility Programming Guide
Alternatively you can make the view subclass not accessible:
- (BOOL)isAccessibilityElement
{
return NO;
}
However, note:
If your application contains a custom individual view with which users need to interact, you must make the view accessible.

Extending existing classes by adding instance variables

in Objective-C if I need to add additional method to UIButton I can use Categories for that. Is there any easy way (except subclassing) to add additional instance variable or property (instance variable + getter/setter).
Let's say I want to store UIColor reference in UIButton.
PS: It is mor theory question. I already implemented that using subclassing but looking for "nicer" way.
Thanks.
One solution is to use associative references; not as fast as ivars, but quite useful.
I.e. given:
#interface UIButton (color)
#property (nonatomic, retain) UIColor *myPrefixColor;
#end
You could implement this as:
#implementation UIButton (color)
static const char *assocKey = "myPrefixColor associated object key";
- (void) setMyPrefixColor: (UIColor*) aColor
{
objc_setAssociatedObject(self, &assocKey, aColor, OBJC_ASSOCIATION_RETAIN);
}
- (UIColor*)myPrefixColor;
{
    return objc_getAssociatedObject(self, &assocKey);
}
#end
The myPrefix stuff is because you should never add methods to existing classes without prefixing 'em with something such that the chance of a collision with an existing method (that you aren't aware of) are minimized.
You cannot add ivars using categories.
I don't think it's possible to add ivars to classes to which you don't have the source. Two technologies come close: Categories let you add instance methods, but not instance variables, and class extensions let you add instance variables by declaring properties, but the compiler enforces the implementation of those properties' methods from within the implementation block of the class, which you don't have access to. They're designed to aid the implementation of private interfaces, not really extend classes that already exist.

Cocoa Touch: can I add a non-integer tag to a UI element?

Is there an easy way for me to attach a property to a given UI element without subclassing it.
What I'd like is basically just like .tag but I'd want it to hold an NSString. the .tag only holds an integer.
Thanks
You could take the approach of adding a Category, possibly something like extendedTag which would provide access to an NSString.
You can use the functionality of associated objects introduced in the IA64 and ARM Objective-C runtimes. This works on on device for iOS, but not on the simulator since the simulator uses the old fragile 32-bit Objective-C run-time.
Here is a small example that allows you to set any kind of object as a context value on all UIView instances. The associated objects are released properly at dealloc, so no extra memory management needed.
// UIView+CWObjectContext.h
#interface UIView (CWObjectContext)
#property(nonatomic, retain) id objectContext;
#end
// UIView+CWobjectContext.m
#import "UIView+CWobjectContext.h"
#import <objc/runtime.h>
#implementation UIView (CWObjectContext)
static void* const objectContextKey = "objectContext";
-(id)objectContext;
{
return objc_getAssociatedObject(self,
objectContextKey);
}
-(void)setObjectContext:(id)object;
{
objc_setAssociatedObject(self,
objectContextKey,
object,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#end
As Jacob replied this isn't possible. However, so I don't get mixed up when using tags, I use
#define EasyToRememberName 5
If I were dynamically trying to apply an NSString in code, I would use an NSDictionary containing the NSStings as keys and use NSNumbers as the tag values. That way I could use
[[exampleDictionary objectForKey:#"tag string"] intValue]
Make a subclass when you feel the need for non-integer tags. That will turn out to be a better approach as the time goes on.

Can I store a custom object inside a button in Objective-C?

In winforms/C# most all UI Controls have a .Tag tag, so like myButton.Tag = myObject; where the Tag property is an 'object' type so you can basically store any type of object. How might I accomplish this in Objective-C/Cocoa? do all UI elements have something like .Tag where I can store an NSObject or something? If so, can you please provide an example. Thanks so much!
Note: I did see the integer .Tag there, but I wanted an object tag. But I guess that doesn't exist. hoo well.
As Georg said, you can associate whatever object to another object using the Objective-C runtime, so you can associate an Object to a control if you really want.
But that is not really how a standard Cocoa program works. Instead, in Cocoa, the Model-View-Controller pattern and the Delegation are the standard idiom, and associating an object directly to a widget or a view is discouraged. Even for a very small program, you would at least create a Model-Controller (called usually the application delegate in the Cocoa jargon) which manages the data, and keep the view composed of the standard controls as is. Then the view and the model-controller interact via target/action and delegation.
Apple has a very nice discussion of design patterns prevalent in Cocoa, see here.
In general, when you move from one API(Winforms/C#) to another API(Cocoa/Objective-C), there are some similarities but also some differences. It is usually worth learning how things are done in that API, rather than trying to shoehorn what you're used to into a new situation. (Just to be clear, I'm not saying which API is inherently better; this discussion goes both ways!)
So, when you are in a situation:
To do X in API A, I know the idiom P works. I now want to do X in API B. How can I directly implement idiom P in API B?
I recommend you to ask
To do X in API B, what should I do? What's the idiom in API B?
instead.
NSControl does have a tag and related setTag: method. It's not used internally so you can store whatever you like in it - it only stores NSInteger values though.
All Cocoa controls inherit from NSControl.
There is the possibility to add a tag, it's an integer if I remember correctly.
This said, I'm pretty sure one never needs this functionality in Cocoa, because it just doesn't work this way.
If you really want to add information you might be interested in the runtime's ability to associate an object with another object.
CALayers have the ability to store arbitrary keys as part of their key-value coding machinery. Example:
CALayer *myLayer = [button layer];
// Storing a value
[layer setValue:#"World!" forKey:#"hello"];
// Retrieving a value
NSLog(#"Hello %#", [layer valueForKey:#"hello"]);
That being said, storing objects against user-interface elements violates the principle of the Model-View-Controller pattern; I would advise against it--a UIView or UIControl subclass would likely be better suited.
Yep. You can add your own property to all UIControls if you like.
Just add the following to your code.
#import <objc/runtime.h>
/* -------- The Following Code adds an objectData property for every UIControl ----------- */
#interface UIControl (UIControlAdditions)
#property (nonatomic, retain) id objectData;
#end
static char const * const ObjectDataKey = "MyObjectDataKey";
#implementation UIControl (UIControlAdditions)
#dynamic objectData;
-(id)objectData {
return objc_getAssociatedObject(self,ObjectDataKey);
}
- (void)setObjectData:(id)newObjectData {
objc_setAssociatedObject(self, ObjectDataKey, newObjectData, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#end
/* -------- The Above Code adds an objectData property for every UIControl ----------- */
Credits to Ole Begemann: http://oleb.net/blog/2011/05/faking-ivars-in-objc-categories-with-associative-references/