zyl1024
zyl1024

Reputation: 700

Fast enumeration does not give correct object type in objective-c?

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 UIImageViews 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 UILabels 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

Answers (6)

Manoj Udupa
Manoj Udupa

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

David Elliman
David Elliman

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

Ben Zotto
Ben Zotto

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

Mundi
Mundi

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

Manish Agrawal
Manish Agrawal

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

Wain
Wain

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

Related Questions