UIView
has a tag
property and a corresponding -viewWithTag:
method. Used together, one can quickly access a specific subview without the need for an explicit property to reference that view.
I’ve seen UIView
’s tag functionality abused in many strange and wonderful ways; so much so that I currently believe it best to avoid completely.
Do not use tags to store data
Let’s start with a ground rule:
UIView
’stag
property should only be used as a unique identifier for a view. It should not store any more information than this.
Seems reasonable doesn’t it? And yet I’ve seen many developers abusing tags to store additional information, such as array indices pointing to model objects:
- (void)configureThumbnailButton:(UIButton *)thumbnailButton forPhoto:(NSUInteger)photoIndex { // ... thumbnailButton.tag = 1 + photoIndex; // ... } - (void)thumbnailButtonTapped:(id)sender { UIButton *thumbnailButton = sender; NSUInteger photoIndex = thumbnailButton.tag - 1; id selectedPhoto = [self.photoArray objectAtIndex:photoIndex]; [self showPhoto:selectedPhoto]; }
This example is setting up a button which displays a thumbnail for a photo. When the user taps the button, the corresponding photo is displayed.
To remember which photo should be displayed when the button is tapped, the tag
property is given a value based on the array index of the photo. It’s common to see a magic number (1 in this case) added to the tag value to disambiguate between views with the default tag value of 0 and a genuine array index.
Using tags like this is pretty nasty. Although I’ve tried to make the above example as clear as possible for demonstration purposes, code that uses tags in this way is often very difficult to follow because there are no useful variable names anywhere.
Let’s take a look at some real-world examples:
[pageScroller scrollRectToVisible:CGRectMake(1024 * (sender.tag - 101), 0, 1024, pageScroller.height) animated:YES];
Another?
NSString *name = ((UITextField *)[((UITableViewCell*)[self.view viewWithTag:i]).contentView viewWithTag:2]).text;
[[[scrollView viewWithTag:100 + currentPage + 2] viewWithTag:22] removeFromSuperview];
homeButton.tag = (int)@"home";
Yes folks, these all come from real production code. Did I mention I love my job?
The correct way to associate extra information with a view
When a developer writes code like this:
NSUInteger photoIndex = thumbnailButton.tag - 1;
What he really wants to write is this:
NSUInteger photoIndex = thumbnailButton.photoIndex;
Or even:
Photo *selectedPhoto = thumbnailButton.photo;
The way to achieve this is obvious. You can create a custom subclass with additional properties to store the extra information you need.
In this case we have a button which is associated with a given photo, so we create a UIButton
subclass and add a property to store the associated photo index, or, even better, a reference to the Photo object itself:
@interface PhotoThumbnailButton : UIButton @property (nonatomic, assign) NSInteger photoIndex; // OR @property (nonatomic, strong) Photo *photo; @end
Now when we want to get or set the associated photo for a button, we just use these public properties. Our code will be much clearer than if we had used tags and array indexes and weird additions and subtractions.
In some cases, creating a subclass is not possible, or it may seem a little overkill given your circumstances. In these cases, you can achieve similar improvements to your code by making use of Objective-C associated references.
Associated references
Associated references make use of objc_setAssociatedObject
and objc_getAssociatedObject
to attach values to an existing object at runtime.
In our case we can write a couple of helper methods like this:
#import <objc/runtime.h> static char kThumbnailButtonAssociatedPhotoKey; // ... - (void)setAssociatedPhoto:(Photo *)associatedPhoto forThumbnailButton:(UIButton *)thumbnailButton { objc_setAssociatedObject(thumbnailButton, &kThumbnailButtonAssociatedPhotoKey, associatedPhoto, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (Photo *)associatedPhotoForThumbnailButton:(UIButton *)thumbnailButton { return objc_getAssociatedObject(thumbnailButton, &kThumbnailButtonAssociatedPhotoKey); }
Now we can easily set/get the associated photo for a button:
- (void)configureThumbnailButtonForPhoto:(Photo *)photo { // ... [self setAssociatedPhoto:photo forThumbnailButton:thumbnailButton]; // ... } - (void)thumbnailButtonTapped { Photo *photo = [self associatedPhotoForThumbnailButton:thumbnailButton]; // ... }
So with a couple of short helper methods, we can easily attach model objects to our UI controls. Associated references are very cool.
The only (semi-)acceptable way to use tags
Let’s assume you aren’t trying to store data in a view’s tag. Instead, you just want a quick and dirty way to grab a reference to a view. Is it OK to use tags in these situations?
Well, in almost every case I can think of, it is better to store a reference to the view using a real property somewhere, whether that’s an IBOutlet
or just a regular property on your class.
Do you need to add some custom views to a UITableViewCell
? Subclass it and add real properties. Need to know which of many UIAlertView
s is calling your alertView:clickedButtonAtIndex:
method? Assign the alerts to properties when you create them, and compare the pointers directly.
By using real properties you get stronger typing, better naming, better visibility of the moving parts of your app, and you don’t have to down-cast viewWithTag
’s UIView*
return type. You also get better performance because viewWithTag:
must traverse the view hierarchy for every call.
To me, using tags seems to be another pattern driven purely by laziness. Adding a property to your parent object is not difficult, especially now that we’ve all upgraded to ARC and Xcode 4.4, where -dealloc
and @synthesize
are unnecessary in most cases. You have upgraded, haven’t you?
But mooooom…
Oh alright then. If you do decide to use tags, you must follow our ground rule and only store unique identifiers for your views. Furthermore, we can say:
UIView
’stag
property should only be given a named#define
orenum
value.
For example, this is good:
enum MyViewTags { kTitleLabelTag = 1, kSendButtonTag, kSomeOtherViewTag }; // ... if (sender.tag == kSendButtonTag) { // ... }
The last thing we want are magic numbers flying around in our code. Always create named constants for your tag values, otherwise you end up with this kind of stuff:
aiView.tag = 22;
content.tag = 111;
globalSyncAlert.tag = 1000;
nextZoomingView.tag = 2000;
infoView.tag = 3000
white.tag = 1111;
howto.tag = 4357824;
imageView.tag = 199;
self.tag = 999;
label.tag = 3542
homeVC.view.tag = 1234;
b.tag = 1983;
Again, these are all real-world examples. I like to collect them.
Summary
In summary, try to avoid using UIView
tags as much as possible. Follow these rules:
- Don’t store data in a view’s tag. Prefer subclassing and Objective-C associated references to associate additional information with a view.
- Prefer real properties and XIB outlets over tags for getting references to subviews.
- If you do use tags, do not use magic numbers. Use named constants.
Follow these rules and your code will be much better off. Your fellow developers will thank you.
Great post dude!
ReplyDeleteLet me also add that is so nice to see an iOS blog talking about good practices and anti-patterns one should avoid when coding for iOS.
Thank you
You've indeed shown very nasty examples of tag abuses... But I think that tag comes in handy for simple purposes, e.g. you need to display a set of thumbnailbuttons, and want to know which one got tapped. Then tagging them 0 - thumbnailCount is fine.
ReplyDeleteIf you come up with a clear tagging convention, wouldn't subclassing be overkill? It would be good to know if the overhead incurred by subclassing is negligible or not. How about some profiling?
For me, tags are super useful to relate views to data. I've created a pretty successful app framework for content-heavy apps which is JSON/XML driven and has been used with content editors manually inputting the information... So, let's say I have 3 xmls, one for building my objects, one for arranging in landscape and one for arranging in portrait -- I'd have no way to relate one view between the three pieces of data without doing a bunch of extra coding and implementing my own 'tag' concept... I guess the UIView tag is still kind of lazy and a custom system would suite my purposes a little better, but it works well :0)
ReplyDeleteI'm glad I found this blog entry. The only reason I'm searching for "IBOutlet vs viewWithTag" is because the Apple doc Concepts in Objective-C Programming reads, "If there are other ways to obtain a reference to an object [aside from IBOutlet], such as finding it through ... use of a tag (an assigned numeric identifier), you should do that instead." Why would they say this?
ReplyDeleteYou can store pointers in the tag property of a UIView. You should cast it to an int. You should also know what to cast the tag property back to when you access it. It works well.
ReplyDeleteI havent tested, but i would guess that might cause problems when targeting 64 bit iPhone 5S...
ReplyDeleteHi, nice article.. very informative.. thanks alot.. by the way is there anyway we could get a link to a properly written code at least with a simple Core data fetching and views handling the right way? or else a tutorial which teaches the new developer to follow these kinda guide lines you mentioned? once again thanks alot Much appreciated..
ReplyDeleteThank you for the article! Although, the pictures are totally inappropriate!
ReplyDeleteMua vé máy bay tại Aivivu, tham khảo
ReplyDeletekinh nghiệm mua vé máy bay đi Mỹ giá rẻ
vé máy bay từ mỹ đi việt nam
thời gian bay từ Việt nam sang Los Angeles
giá vé máy bay từ Vancouver về việt nam
WinStar World Casino Tickets - Ticketmaster
ReplyDeleteBuy 이천 출장샵 WinStar World Casino 동두천 출장안마 tickets at 평택 출장마사지 Ticketmaster.com. Find the latest 대전광역 출장안마 gaming and entertainment news, ticket deals and online shopping 강릉 출장안마 at Ticketmaster.com.
김천출장샵
ReplyDelete김천출장샵
전북출장샵
나주출장샵
나주출장샵
문경출장샵
완주출장샵
문경출장샵
속초출장안마
ReplyDelete청도출장안마
용인출장안마
속초출장안마
동해출장안마
고령출장안마
파주출장안마
파주출장안마
yurtdışı kargo
ReplyDeleteresimli magnet
instagram takipçi satın al
yurtdışı kargo
sms onay
dijital kartvizit
dijital kartvizit
https://nobetci-eczane.org/
JWQU
salt likit
ReplyDeletesalt likit
dr mood likit
big boss likit
dl likit
dark likit
EERCT5
THE ONE CARGO บริการ สั่งของจากจีน นำเข้าสินค้าจากจีน เหมาะสำหรับลูกค้าที่สั่งสินค้า และติดต่อกับร้านจีนได้เอง เราพร้อมเป็นผู้ดูแล ส่วนของการ นำเข้าสินค้าจากจีน แบบครบวงจร ตั้งแต่การรับสินค้า แยกสินค้า พร้อมทั้งจัดการภาษี
ReplyDelete