Tipo casting en bucle for-in

116

Tengo este bucle for-in:

for button in view.subviews {
}

Ahora quiero que el botón se convierta en una clase personalizada para poder usar sus propiedades.

Intenté esto: for button in view.subviews as AClass

Pero no funciona y me da un error:'AClass' does not conform to protocol 'SequenceType'

Y probé esto: for button:AClass in view.subviews

Pero eso tampoco funciona.

Arbitur
fuente
Qué talfor button in view.subviews as [AClass]
vacawama
2
jaja, no pensé en eso, por supuesto que es mejor lanzar la matriz en una matriz de AClass, gracias tío. Puede dar una respuesta al respecto para que pueda darle un poco de representante :)
Arbitur

Respuestas:

173

Para Swift 2 y posteriores:

Swift 2 agrega patrones de caso a los bucles for , lo que hace que sea aún más fácil y seguro escribir cast en un bucle for :

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

¿Por qué esto es mejor que lo que podría hacer en Swift 1.2 y versiones anteriores? Porque los patrones de caja le permiten elegir su tipo específico de la colección. Solo coincide con el tipo que está buscando, por lo que si su matriz contiene una mezcla, puede operar solo en un tipo específico.

Por ejemplo:

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

Salida:

Hello
World!

Para Swift 1.2 :

En este caso, está transmitiendo view.subviewsy no button, por lo que debe reducirlo a la matriz del tipo que desea:

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

Nota: Si el tipo de matriz subyacente no lo es [AClass], se bloqueará. Eso es lo que te está diciendo el !on as!. Si no está seguro del tipo, puede usar una conversión condicional as?junto con un enlace opcional 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
    }
}

Para Swift 1.1 y versiones anteriores:

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

Nota: Esto también fallará si las subvistas no son todas de tipo AClass. El método seguro mencionado anteriormente también funciona con versiones anteriores de Swift.

vacawama
fuente
Solo una nota para otros como yo que no la entendieron al principio: el nombre de la clase debe ir entre [ ]corchetes exactamente como en este ejemplo. Tal vez sea solo yo, pero pensé que era solo una opción de formato en el ejemplo de código al principio :) Supongo que esto se debe a que estamos transmitiendo a una matriz.
@kmcgrady Se [Class]ve mucho mejor que Array<Class>.
Arbitur
Entonces, por curiosidad, ¿hay alguna manera de hacer múltiples declaraciones de casos dentro del ciclo for? Por ejemplo, si quiero hacer una cosa con los botones y otra con los campos de texto.
RonLugge
125

Esta opción es más segura:

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

o manera rápida:

view.subviews
  .compactMap { $0 as AClass }
  .forEach { .... }
ober
fuente
1
Esta parece la mejor respuesta, ya que no hay un lanzamiento de fuerza.
Patrick
1
Esta debería ser la respuesta aceptada. ¡Es el mejor y el correcto!
Thomás Calmon
Respuesta correcta. Corto y sencillo.
PashaN
4

También puedes usar una wherecláusula

for button in view.subviews where button is UIButton {
    ...
}
edelaney05
fuente
12
La cláusula where es una protección booleana, pero no lanza el tipo de buttondentro del cuerpo del bucle, que es el objetivo de las otras soluciones. Por lo tanto, esto solo ejecutará el cuerpo del bucle en elementos de los view.subviewscuales son UIButtons, pero no ayuda, por ejemplo, en llamar a AClassmétodos específicos button.
John Whitley
1
@JohnWhitley tienes razón en que wheresolo protege y no lanza.
edelaney05
wherepuede ser más elegante y legible para los casos (juego de palabras involuntario) en los que solo necesita una protección booleana.
STO
3

Las respuestas proporcionadas son correctas, solo quería agregar esto como una adición.

Cuando se usa un bucle for con conversión forzada, el código se bloqueará (como ya lo mencionaron otros).

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

Pero en lugar de usar una cláusula if,

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

otra forma es usar un ciclo while:

/* 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
    // ...
}

Lo que parece ser más conveniente si algunos elementos (pero no todos) de la matriz pueden ser de tipo AClass.

Aunque por ahora (a partir de Swift 5), optaría por el bucle for-case:

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

for case let subview as AClass in view.subviews {
    // ...
}
user0800
fuente
Solo una duda aquí ... ¿la función enumerada rápida realiza iteraciones / bucle aquí ... solo pensar que la refactorización para perder rendimiento no será genial ... gracias
Amber K
2

La respuesta proporcionada por vacawama fue correcta en Swift 1.0. Y ya no funciona con Swift 2.0.

Si lo intenta, obtendrá un error similar a:

'[AnyObject]' no se puede convertir en '[AClass]';

En Swift 2.0 necesitas escribir como:

for button in view.subviews as! [AClass]
{
}
MP Midhun
fuente
0

Puedes realizar el casting y estar seguro al mismo tiempo con esto:

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

}
nandodelauni
fuente