Digita casting nel ciclo for-in


116

Ho questo ciclo di for-in:

for button in view.subviews {
}

Ora voglio che il pulsante venga convertito in una classe personalizzata in modo da poter utilizzare le sue proprietà.

Ho provato questo: for button in view.subviews as AClass

Ma non funziona e mi dà un errore:'AClass' does not conform to protocol 'SequenceType'

E ho provato questo: for button:AClass in view.subviews

Ma neanche questo funziona.


Che ne dicifor button in view.subviews as [AClass]
vacawama

2
haha non ci ho pensato, ovviamente è meglio lanciare l'array in un array di AClass, grazie amico. Puoi dare una risposta al riguardo così posso darti un rappresentante :)
Arbitur

Risposte:


173

Per Swift 2 e versioni successive:

Swift 2 aggiunge modelli di case ai loop for , il che rende ancora più facile e sicuro digitare cast in un ciclo for :

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

Perché è meglio di quello che potresti fare in Swift 1.2 e versioni precedenti? Perché i modelli di custodia ti consentono di scegliere il tuo tipo specifico dalla collezione. Corrisponde solo al tipo che stai cercando, quindi se il tuo array contiene una miscela, puoi operare solo su un tipo specifico.

Per esempio:

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

Produzione:

Hello
World!

Per Swift 1.2 :

In questo caso, stai eseguendo il casting view.subviewse non button, quindi devi scaricarlo nell'array del tipo che desideri:

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

Nota: se il tipo di array sottostante non è [AClass], si verificherà un arresto anomalo. Questo è ciò che l' !on as!che si sta dicendo. Se non sei sicuro del tipo, puoi utilizzare un cast condizionale as?insieme all'associazione facoltativa 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
    }
}

Per Swift 1.1 e versioni precedenti:

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

Nota: anche questo andrà in crash se le sottoview non sono tutte di tipo AClass. Il metodo sicuro sopra elencato funziona anche con le versioni precedenti di Swift.


Solo una nota per gli altri come me che all'inizio non l'hanno capito: il nome della classe deve essere racchiuso [ ]tra parentesi esattamente come in questo esempio. Forse sono solo io, ma all'inizio ho pensato che fosse solo una scelta di formattazione nell'esempio di codice :) Presumo che sia perché stiamo eseguendo il casting su un array.

@kmcgrady [Class]Sembra molto meglio di Array<Class>.
Arbitur

Quindi, per curiosità, c'è un modo per fare più istruzioni case all'interno del ciclo for? Ad esempio, se voglio fare una cosa ai pulsanti, un'altra ai campi di testo?
RonLugge

125

Questa opzione è più sicura:

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

o modo rapido:

view.subviews
  .compactMap { $0 as AClass }
  .forEach { .... }

1
Questa sembra la risposta migliore in quanto non vi è alcun cast di forza.
Patrick

1
Questa dovrebbe essere la risposta accettata. È il migliore e quello corretto!
Thomás Calmon

Risposta corretta. Breve e semplice.
PashaN

4

Puoi anche usare una whereclausola

for button in view.subviews where button is UIButton {
    ...
}

12
La clausola where è una guardia booleana, ma non esegue il cast del tipo di buttonall'interno del corpo del ciclo, che è l'obiettivo delle altre soluzioni. Quindi questo eseguirà il corpo del ciclo solo sugli elementi di view.subviewscui sono UIButtons, ma non aiuta, ad esempio, a chiamare AClassmetodi -specific su button.
John Whitley

1
@JohnWhitley hai ragione sul fatto che si wherelimita a fare la guardia e non lancia.
edelaney05

wherepuò essere più elegante e leggibile per i casi (gioco di parole non intenzionale) in cui è necessaria solo una guardia booleana.
STO

3

Le risposte fornite sono corrette, volevo solo aggiungerla come aggiunta.

Quando si utilizza un ciclo for con force casting, il codice andrà in crash (come già menzionato da altri).

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

Ma invece di usare una clausola if,

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

un altro modo è usare 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
    // ...
}

Il che sembra essere più conveniente se alcuni elementi (ma non tutti) dell'array potrebbero essere di tipo AClass.

Anche se per ora (a partire da Swift 5), sceglierei il ciclo for-case:

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

for case let subview as AClass in view.subviews {
    // ...
}

Solo un dubbio qui ... la funzione rapida enumerata esegue iterazioni / loop qui .. solo pensare che il refactoring per perdere prestazioni non sarà eccezionale ... grazie
Amber K

2

La risposta fornita da vacawama era corretta in Swift 1.0. E non funziona più con Swift 2.0.

Se ci provi, otterrai un errore simile a:

"[AnyObject]" non è convertibile in "[AClass]";

In Swift 2.0 devi scrivere come:

for button in view.subviews as! [AClass]
{
}

0

Puoi eseguire il casting ed essere al sicuro allo stesso tempo con questo:

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

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.