Rzutowanie typu w pętli for-in

116

Mam tę pętlę for-in:

for button in view.subviews {
}

Teraz chcę, aby przycisk został rzutowany na klasę niestandardową, aby móc użyć jego właściwości.

Próbowałem tego: for button in view.subviews as AClass

Ale to nie działa i wyświetla mi błąd:'AClass' does not conform to protocol 'SequenceType'

I spróbowałem tego: for button:AClass in view.subviews

Ale to też nie działa.

Arbitur
źródło
Co powiesz nafor button in view.subviews as [AClass]
vacawama
2
haha nie pomyślałem o tym, oczywiście lepiej jest rzucić tablicę na tablicę AClass, dzięki stary. Możesz udzielić odpowiedzi na ten temat, żebym mógł Ci przyznać :)
Arbitur

Odpowiedzi:

173

Dla Swift 2 i nowszych:

Swift 2 dodaje wzory przypadków do pętli for , dzięki czemu wpisywanie rzutów w pętli for jest jeszcze łatwiejsze i bezpieczniejsze :

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

Dlaczego jest to lepsze niż to, co można było zrobić w wersji Swift 1.2 i wcześniejszych? Ponieważ wzory kopert pozwalają na wybranie konkretnego typu z kolekcji. Pasuje tylko do typu, którego szukasz, więc jeśli twoja tablica zawiera mieszaninę, możesz operować tylko na określonym typie.

Na przykład:

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

Wynik:

Hello
World!

Dla Swift 1.2 :

W tym przypadku rzutujesz, view.subviewsa nie button, więc musisz obniżyć go do tablicy odpowiedniego typu:

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

Uwaga: jeśli podstawowy typ tablicy nie jest [AClass], to się zawiesi. To, co !on as!ci mówi. Jeśli nie masz pewności co do typu, możesz użyć rzutowania warunkowego as?wraz z opcjonalnym wiązaniem 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
    }
}

Dla Swift 1.1 i starszych:

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

Uwaga: spowoduje to również awarię, jeśli podglądy nie są wszystkich typów AClass. Wymieniona powyżej bezpieczna metoda działa również z wcześniejszymi wersjami języka Swift.

vacawama
źródło
Tylko uwaga dla innych, takich jak ja, którzy na początku tego nie rozumieli - nazwa klasy musi być umieszczona w [ ]nawiasach dokładnie tak, jak w tym przykładzie. Może to tylko ja, ale na początku pomyślałem, że to tylko wybór formatowania w przykładowym kodzie :) Zakładam, że dzieje się tak dlatego, że rzutujemy na tablicę.
@kmcgrady Po prostu [Class]wygląda znacznie lepiej niż Array<Class>.
Arbitur
A więc z ciekawości, czy istnieje sposób na wykonanie wielu instrukcji case w pętli for? Np. Jeśli chcę zrobić jedną rzecz z przyciskami, a inną z polami tekstowymi?
RonLugge,
125

Ta opcja jest bezpieczniejsza:

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

lub szybki sposób:

view.subviews
  .compactMap { $0 as AClass }
  .forEach { .... }
ober
źródło
1
Wydaje się, że jest to lepsza odpowiedź, ponieważ nie ma rzutu na siłę.
Patrick,
1
To powinna być akceptowana odpowiedź. Jest najlepszy i właściwy!
Thomás Calmon,
Poprawna odpowiedź. Krótkie i proste.
PashaN
4

Możesz także użyć whereklauzuli

for button in view.subviews where button is UIButton {
    ...
}
edelaney05
źródło
12
Klauzula where jest logiczną ochroną, ale nie rzutuje typu buttonwewnątrz treści pętli, co jest celem innych rozwiązań. Więc to uruchomi treść pętli tylko na elementach, view.subviewsktórych są UIButtons, ale nie pomoże np. W wywołaniu AClassmetod specyficznych dla button.
John Whitley
1
@JohnWhitley masz rację, że wheretylko strzeże i nie rzuca.
edelaney05
wheremoże być bardziej elegancki i czytelny w przypadkach (niezamierzona gra słów), w których potrzebujesz tylko boolowskiego strażnika.
STO,
3

Podane odpowiedzi są poprawne, chciałem tylko dodać to jako dodatek.

Podczas używania pętli for z rzutowaniem wymuszonym kod ulegnie awarii (jak już wspominali inni).

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

Ale zamiast używać klauzuli if,

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

innym sposobem jest użycie pętli 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
    // ...
}

Co wydaje się być wygodniejsze, jeśli niektóre elementy (ale nie wszystkie) tablicy mogą być typu AClass.

Chociaż na razie (od Swift 5) wybrałbym pętlę for-case:

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

for case let subview as AClass in view.subviews {
    // ...
}
user0800
źródło
Tylko jedna wątpliwość ... czy szybka, wyliczona funkcja wykonuje tutaj iteracje / pętlę ... samo myślenie, że refaktoryzacja w celu utraty wydajności nie będzie świetna ... dzięki
Amber K
2

Odpowiedź udzielona przez vacawama była poprawna w Swift 1.0. I nie działa już ze Swift 2.0.

Jeśli spróbujesz, pojawi się błąd podobny do:

„[AnyObject]” nie można zamienić na „[AClass]”;

W Swift 2.0 musisz pisać tak:

for button in view.subviews as! [AClass]
{
}
Midhun MP
źródło
0

Możesz wykonać rzut i jednocześnie być bezpiecznym:

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

}
nandodelauni
źródło