Reputation: 700
I think this is pretty weird. So I have a UIView
and I want to change the textcolor
of all UILabel
. Here is what I did:
for (UILabel *label in [self subviews]) { // self is the UIView
label.textColor = someColor;
}
When I run the code, it crashed with error like UIImageView: unrecognized selector setTextColor: sent to instance (some instance)
So it seems that the label
in the fast enumeration is actually a UIImageView
. By the way, I do have two UIImageView
s in the UIView *self
. However, shouldn't the fast enumeration give only UILabel
only (because I specified UILabel *label
instead of UIView *label
)?
I think this is the problem because when I wrote the following code, it works.
for (UILabel *label in [self subviews]) { // self is the UIView
if ([label isKindOfClass:[UILabel class]]) {
label.textColor = someColor;
}
}
So in this code, when I check to guarantee that label
is a UILabel
and then set its textcolor
, all UILabel
s in the view changes their color correctly.
Can someone explain why I need the if-statement
to double-check the instance type?
Upvotes: 1
Views: 356
Reputation: 60
In objective C we do not have type casting. Just by using fast enumeration and assigning that variable to UILabel doesnot typecast it to UILabel. Thats the reason for the crash. We can either use respondsToSelector or as you have used isKindOfClass to make the code work as expected.
Upvotes: 0
Reputation: 1399
[self subviews] returns an NSArray which contains (id) pointers to UIViews. These could be any type of View including UILabels. So if a particular subview does not support setTextColor you will get an unrecognised selector message. So you do need your isKindOfClass test or you could use a respondsToSelector test to be more general.
Upvotes: 0
Reputation: 71058
You're reading the enumeration as "loop for every UILabel in my subviews", but that's not how it really works-- fast enumeration doesn't do any smart filtering, it's just a shortcut. You should read it as "loop for every object in my subviews, which are UILabels". And because not every subview is in fact a UILabel, you have problems when trying to treat them all as one.
Every object in that array will be a UIView
though, so the more telling syntax would be:
for (UIView * view in self.subviews) {
if ([view isKindOfClass:[UILabel class]]) {
UILabel * label = (UILabel *)view;
// do something with label...
}
}
Upvotes: 5
Reputation: 80271
More elegant: tag your labels.
#define kLabelOffset 100;
for (int i=kLabelOffset +1; i < totalLabels; i+) {
UILabel *label = (UILabel*) [self viewWithTag:i];
label.textColor = someColor;
}
Upvotes: 0
Reputation: 11037
in the first loop your are iterating for all subviews of the main view while in the second for loop you are still iterating for all the objects but modifying only UILabel type elements.
Your main view can contain all types of views like UIImageView UILabel etc.....Enumerating over UIlabel as a datatype doesn't actually change the datatype of it and not going to enumerate over only that type of elements..
Upvotes: 2
Reputation: 119041
Your 2 loops are very different. The first assumes that all subviews will be labels, the second checks each subview to ensure it is a label. The first should check too.
Fast enumeration is basically syntactic sugar, it's just a normal for
loop. It doesn't magically filter or manipulate the list you give it to enumerate.
Upvotes: 5