For-in loop and type casting only for objects which match type
You can use a for-loop with a case-pattern:
for case let item as YourType in array {
// `item` has the type `YourType` here
// ...
}
This will execute the loop body only for those items in the
array which are of the type (or can be cast to) YourType
.
Example (from
Loop through subview to check for empty UITextField - Swift):
for case let textField as UITextField in self.view.subviews {
if textField.text == "" {
// ...
}
}
Type casting in for-in loop
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.
TypeScript type in a for... in loop on an object
As the TypeScript error says, "The left-hand side of a 'for...in' statement cannot use a type annotation."
However, you can create a typed variable within the for...in
loop that does have a type, and use that to index your object.
Here's a modified version of the snippet in your question. I've added an empty data
variable and removed the querySplit
code because those parts don't have the necessary context in your snippet to resolve TypeScript errors.
You'll also note I had to replace your event[a]
code with a variable, because accessing the value using square bracket notation repeatedly doesn't work properly with TypeScript's type narrowing.
interface EVENT {
imageURL: string;
artist: string;
location: string;
city: string;
seat: number;
direction: string;
country: string;
type: string;
date: string;
tickets_available: number;
tickets_left: number;
id: string;
description: string;
price: number;
}
// Just creating an empty array so TypeScript doesn't complain about `data`
let data: EVENT[] = [];
data.filter((event: EVENT) => {
// a = key of the object data
for (let a in event) {
// Create new typed variable for this iterations' value of `a`
const key = a as keyof EVENT;
// Look up `event[key]` once so TypeScript can narrow its type
const value = event[key];
let aSplit = typeof value === "string"
? value.split(" ").map((element: string) => element.toLowerCase())
: value;
// More stuff here
}
});
TypeScript Playground
Why scala's pattern maching does not work in for loops for type matching?
This is the long-standing issue 900 and has been discussed many times before. The common workaround is to use something like:
for (y@(_y:String) <- listOfBaseObjects) {
println(y)
}
A nicer version is provided by Jason Zaugg in the comments to the above-mentioned ticket:
object Typed { def unapply[A](a: A) = Some(a) }
for (Typed(y : String) <- listOfBaseObjects) {
println(y)
}
Objective-C Using for-in Loop Making the Loop Enter Only if the Object is of a Certain Type
Just check for the correct class yourself, by using isKindOfClass:
for (MYObject* myObj in simpleArray) {
if (![myObj isKindOfClass:[MYObject class]]) {
// wrong class. continue to next object.
continue;
}
sumNums += myObj.myNum;
}
How to iterate over an array and check element type at the same time in swift?
Solution 1
You can filter your array
let things = [NSDictionary(), NSArray(), NSDictionary(), NSArray()]
let dicts = things.flatMap { $0 as? NSDictionary }
Now dicts
is defined as [NSDictionary]
an contains only the 2 dictionaries.
Solution 2
You can also perform a for loop only on the values that are dictionaries
let things = [NSDictionary(), NSArray(), NSDictionary(), NSArray()]
for dict in things.flatMap( { $0 as? NSDictionary }) {
}
Pattern matching in a Swift for loop
You can combine a pattern matching conditional cast with a where
clause like so:
let myStuff: [AnyObject] = [5, "dog", 11, 15, "cat"]
// item will be an Int, and divisible by 5
for case let item as Int in myStuff where item % 5 == 0 {
print(item)
}
// Prints:
// 5
// 15
How to assert matching types when iterating over an object?
It is worth using reduce
instead of forEach
in this case:
interface IMixed {
a: number;
b: string;
c: boolean;
}
const foo: IMixed = { a: 1, b: 'one', c: true };
const bar: IMixed = { a: 2, b: 'two', c: false };
(Object.keys(foo) as Array<keyof IMixed>)
.reduce((acc, elem) => ({
...acc,
[elem]: bar[elem]
}), foo);
Playground
Mutations does not work well in TypeScript.
See related questions:
first, second, third, forth and my article
UPDATE
(Object.keys(foo) as Array<keyof IMixed>).forEach(k => {
foo[k] = bar[k]; // error
});
You have an error here, because forEach
as well as reduce
is dynamic.bar[k]
is a union of number | string | boolean
, as well as foo[k]
. It means that inside the loop it would be possible to assign foo['a'] = foo['c']
. Both values are valid, since we expect a union of number | string | boolean
but it also would be unsafe. That's why TS forbids this behavior.
From the other hand, reduce
works, because we create new object which extends IMixed
instead of mutating
UPDATE
In order to be able to mutate foo
you need add indexing
to IMixed
interface:
type IMixed= {
a: number;
b: string;
c: boolean;
[prop: string]: number | boolean | string
}
const foo: IMixed = { a: 1, b: 'one', c: true };
const bar: IMixed = { a: 2, b: 'two', c: false };
(Object.keys(foo) as Array<keyof IMixed>).forEach(k => {
foo[k] = bar[k];
});
Playground
Related Topics
Create View Based Nstableview Programmatically Using Bindings in Swift
Function That Takes a Protocol and a Conforming Class (!) Instance as Parameters
Nsimage Getting Resized When I Draw Text on It
How to Read a Property List from Data in Swift 3
How to Mimic 'Uitableviewcontroller' Showing of the Large Titles in 'Navigationbar' on iOS 11
Define a Swift Protocol Which Requires a Specific Type of Sequence
What Are the Advantages Swift Deprecates C-Style for Statement
No Value Associated with Key Codingkeys While Trying to Get Data from Github API in Xcode App
Using Scenekit in Swift Playground
Lazy Property Initialization in Swift
How Are Int and String Accepted as Anyhashable
Difference Between Nsrange and Nsmakerange
Iphonex Not Call Prefersstatusbarhidden
Appending Tuples to an Array of Tuples
Compare Three Values for Equality
Pattern Match and Conditionally Bind in a Single Switch Statement