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

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/

Related

Internal properties versus ivars

When I need a private object I currently use properties, like so:
// Class extension in .m file
#interface MyClass()
#property (strong, nonatomic) NSArray* myInternalArray;
#end
self.myInternalArray = something;
Alternatively you can do this:
#implementation MyClass {
NSArray* _myInternalArray;
}
_myInternalArray = something;
Without a custom setter or getter the two are equivalent. What is the best practice for internal variables? Are there any advantages of one method over the other?
While some may argue that the choice is a matter of preference, and they do have a point, there is a very good reason that most modern languages support properties and make them easier and easier to code.
The introduction of ARC does not significantly reduce the value of properties. It all comes down to this - in a property you have encapsulated the use of a variable. That encapsulation is invaluable when needed, and not much overhead when it is not.
For example (off of the top of my head) Suppose you discovered that you needed to validate the value before saving it. If you were using an iVar, you would have to ensure that anywhere that iVar was used, you had a call the validation code before you allowed it's value to be changed. With a property, you would only need to override setIVarName: and put the validation there. One could argue that one is just as easy as the other - and that may be true in many cases, but there is one handicap with the iVar here - you cannot ensure that future changes (by you or other coders) will insert the validation before the iVar is changed. Using a property here does have that assurance.
Personally, I use properties over iVars where ever possible.
I'd say that the advantage of properties is that you would use setters, and that setters can evolve independently of the code that call them. For instance, you could decide that setting a property would now trigger setNeedsLayout. By using properties from the start, you would have no need to refactor existing code.
This pattern fits very well in Cocoa/iOS APIs, where you don't have to ask system objects to do anything after having changed their properties: setters ensure internal and UI consistency right away.
The fact that properties are private should not make us implement them as second-class properties, what do you think?

passing arguments by a button's tag in objective-c

I saw someone passing arguments by a button's tag as follow.
how could this work? and is it safe to do like this ?
NSCustomClass* cls = [[NSCustomClass alloc] init:#"",#"",#""];
[button setTag:(int) cls];
- (void)OnClick:(id)sender
{
NSCustomClass* cls = (NSCustomClass*)[sender tag];
// to do something with the "cls".
[cls release];
}
In fact,I didn't get weird results.
It works fine by passing arguments like 'NSString','NSArray' in this way.
Can you tell me why it is a bad idea?
Casting a custom class to an int is a very bad idea and you'll get weird results.
The tag is an integer property on all UI elements. It is declared as such on UIView:
#property(nonatomic) NSInteger tag;
You can assign any integer value to it, including any predefined constants:
#define Button1Constant 1
#define PushButtonConstant 2
// …Code
[button setTag:PushButtonConstant];
// …More code
if (sender.tag == PushButtonContent)
// Handle
In general you never want to abuse the frameworks. The tag is intended to store an integer and is used mainly to access a view with viewWithTag:, which can be useful in some cases if your view was built in Interface Builder but a referencing IBOutlet is inappropriate. Stuffing a pointer into an int can give unpredictable results, as others have indicated.
On top of that there's a memory management issue. Even if the pointer survives the cast you aren't retaining the object being pointed to. This in effect is a weak pointer but without any management by the run-time. Attempts to use the pointer will likely lead to the dreaded EXC_BAD_ACCESS. And who knows what ARC might do with this mess - blow up, I would expect.
To attach data to a button (which in and of itself sounds like a possible design flaw) you should either subclass or leverage the power of the run-time with objc_setAssociatedObject() and friends.
In general, casting from a pointer type to an integer type usually indicates a design flaw; there are very few legitimate reasons to do this, and I would strongly discourage doing it, especially in this case. It may work, but it's not guaranteed to work, simply because the tag property is not documented to be used this way. The more “hacks” that you put in your code, the harder it is to maintain.
Don't learn from the code where you saw this, instead stick to reputable tutorials and books.
As a side note, it is conventional in Objective-C to name all methods starting with a lowercase letter, so the method should be onClick: and not OnClick:. The exception to this rule is when the method starts with an acronym, for example -[NSString UTF8String].
You could subclass from UIButton and define a property by yourself, instead of using its tag property.

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.

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

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

myView.frame Vs. self.myView.frame

I know it's often advisable to use the #property accessors, and I know it's important to use self.myView = x instead of mView = x, but:
Isn't myView.frame sufficient? The AtomicElementFlippedView.m file in TheElements has self.wikipediaButton.frame=buttonFrame;. Shouldn't I leave out self if I'm getting/setting a property of my ivar and not the ivar itself?
It also does [self.wikipediaButton addTarget:...];. But shouldn't I leave out self here as well? Shouldn't I always call a function on the ivar, not the property? I.e., [wikipediaButton addTarget:...];
Thanks!
Matt
Both will work.
The main reason for using getters within a class is that code changes are easy. If you decide to store your data in a different fashion or to build it dynamically on request, there is a single point of change. With views it's not that important most of the time, but it can come handy when you replace that view with a complete view hierarchy (your original view being maybe part of it).
I'd say the main reason you'd want to use the property setter instead of direct ivar access is simply consistency. Yes, you can access the ivar directly, and you'll usually be just fine doing so. However, I'd say it's stylistically better to use the same method for both setting and getting. So if you're using property setters internally, you should also use property getters internally.