132

I have this for-in loop:

for button in view.subviews {
}

Now I want button to be cast into a custom class so I can use its properties.

I tried this: for button in view.subviews as AClass

But it doesnt work and gives me an error:'AClass' does not conform to protocol 'SequenceType'

And I tried this: for button:AClass in view.subviews

But neither does that work.

Arbitur
  • 38,684
  • 22
  • 91
  • 128
  • How about `for button in view.subviews as [AClass]` – vacawama Sep 27 '14 at 16:37
  • 2
    haha I didnt think of that, of course its better to cast the array into an array of AClass, thanks man. You can make an answer about it so I can give you some rep :) – Arbitur Sep 27 '14 at 16:39
  • Compare http://stackoverflow.com/questions/25097958/loop-through-subview-to-check-for-empty-uitextfield-swift/25098005#25098005. – Martin R Aug 07 '16 at 10:40

7 Answers7

209

For Swift 2 and later:

Swift 2 adds case patterns to for loops, which makes it even easier and safer to type cast in a for loop:

for case let button as AClass in view.subviews {
    // do something with button
}

Why is this better than what you could do in Swift 1.2 and earlier? Because case patterns allow you to pick your specific type out of the collection. It only matches the type you are looking for, so if your array contains a mixture, you can operate on only a specific type.

For example:

let array: [Any] = [1, 1.2, "Hello", true, [1, 2, 3], "World!"]
for case let str as String in array {
    print(str)
}

Output:

Hello
World!

For Swift 1.2:

In this case, you are casting view.subviews and not button, so you need to downcast it to the array of the type you want:

for button in view.subviews as! [AClass] {
    // do something with button
}

Note: If the underlying array type is not [AClass], this will crash. That is what the ! on as! is telling you. If you're not sure about the type you can use a conditional cast as? along with optional binding if let:

if let subviews = view.subviews as? [AClass] {
    // If we get here, then subviews is of type [AClass]
    for button in subviews {
        // do something with button
    }
}

For Swift 1.1 and earlier:

for button in view.subviews as [AClass] {
    // do something with button
}

Note: This also will crash if the subviews aren't all of type AClass. The safe method listed above also works with earlier versions of Swift.

vacawama
  • 150,663
  • 30
  • 266
  • 294
  • Just a note for others like me who didn't get it at first - the class name has to go inside ```[ ]``` brackets exactly like in this example. Maybe it's just me but I thought it was just a formatting choice in the code sample at first :) I presume this is because we are casting to an array. –  Jun 05 '15 at 09:54
  • @kmcgrady Its just `[Class]` looks much better than `Array`. – Arbitur Jul 14 '15 at 14:23
  • So out of curiosity, is there a way to do multiple case statements inside the for loop? E. G. if I want to do one thing to buttons, another to text fields? – RonLugge Nov 18 '17 at 00:59
133

This option is more secure:

for case let button as AClass in view.subviews {
}

or swifty way:

view.subviews
  .compactMap { $0 as AClass }
  .forEach { .... }
ober
  • 2,363
  • 1
  • 19
  • 17
6

The answers provided are correct, I just wanted to add this as an addition.

When using a for loop with force casting, the code will crash (as already mentioned by others).

for button in view.subviews as! [AClass] {
    // do something with button
}

But instead of using an if-clause,

if let subviews = view.subviews as? [AClass] {
    // If we get here, then subviews is of type [AClass]
    ...
}

another way is to use a while-loop:

/* If you need the index: */
var iterator = view.subviews.enumerated().makeIterator()
while let (index, subview) = iterator.next() as? (Int, AClass) {
    // Use the subview
    // ...
}

/* If you don't need the index: */
var iterator = view.subviews.enumerated().makeIterator()
while let subview = iterator.next().element as? AClass {
    // Use the subview
    // ...
}

Which seems to be more convenient if some elements (but not all) of the array might be of type AClass.

Although for now (as of Swift 5), I'd go for the for-case loop:

for case let (index, subview as AClass) in view.subviews.enumerated() {
    // ...
}

for case let subview as AClass in view.subviews {
    // ...
}
j3141592653589793238
  • 1,810
  • 2
  • 16
  • 38
  • Just one doubt here...does the swift enumerated function performs iterations/loop here.. just thinking refactoring for losing performance will not be great...thanks – Amber K Feb 15 '20 at 17:54
4

You can also use a where clause

for button in view.subviews where button is UIButton {
    ...
}
Arbitur
  • 38,684
  • 22
  • 91
  • 128
edelaney05
  • 6,822
  • 6
  • 41
  • 65
  • 17
    The where clause is a boolean guard, but does not cast the type of `button` inside the loop body, which is the goal of the other solutions. So this will only run the loop body on elements of `view.subviews` which are `UIButton`s, but doesn't help e.g. in calling `AClass`-specific methods on `button`. – John Whitley Oct 28 '16 at 18:35
  • 1
    @JohnWhitley you are correct that `where` only guards and does not cast. – edelaney05 Oct 30 '16 at 02:52
  • `where` may be more elegant and readable for cases (pun unintentional) where you need only a boolean guard. – STO Sep 07 '18 at 07:57
3

You can perform the casting and be safe at the same time with this:

for button in view.subviews.compactMap({ $0 as? AClass }) {

}
nandodelauni
  • 291
  • 1
  • 10
2

The answer provided by vacawama was correct in Swift 1.0. And no longer works with Swift 2.0.

If you try, you will get an error similar to:

'[AnyObject]' is not convertible to '[AClass]';

In Swift 2.0 you need to write like:

for button in view.subviews as! [AClass]
{
}
Midhun MP
  • 103,496
  • 31
  • 153
  • 200
2

If you want to have an index in your loop:

for case let (index, button as AClass) in view.subviews.enumerated() {
}