Leave a rating/review
Notes: 14. forEach & map
Update Notes: This course was originally recorded in 2019. It has been reviewed and all content and materials updated as of October 2021.
Now that you know a bit about closures, you’re ready to see how they can be particularly useful when dealing with collections. Collections in Swift have several handy methods that take a closure as an argument. These closures are executed for each element in the collection, much like the body of a for loop!
Two common methods like this are forEach and map. forEach and a for loop are nearly interchangeable. They both iterate over a collection, pass each element into a block of code, and then run that code.
map works nearly the same, with one important difference. forEach doesn’t return anything, but map returns a collection. Each iteration returns an element that is added to the final, returned collection.
map is a great way to apply a some change to a collection of data, or transform a collection of one type into another type.
For example, if you followed the Your Second iOS & SwiftUI App course, then you used map to turn an array of Strings into an array of Tasks!
init(priority: Task.Priority, names: [String]) {
self.init(
priority: priority,
tasks: names.map 💛{ Task(name: $0) }💛
)
}
Let’s try out forEach and map in a playground.
For most of these methods, we’ll try to get the results from a for loop, first. To start out with a simple example, we’ll just print out all of the elements of this prices array.
for price in prices {
print(price)
}
To do the same with forEach, start with the collection you want to iterate over. In this case, that’s the prices array.
prices
Then call the forEach method on that collection!
prices.forEach(
You can see this method takes one closure (or function) as an argument. That closure will take the same type as the elements in the collection, and return nothing.
In this case, prices is an array of Doubles, so the closure takes a Double. Hit return and let the closure auto-complete:
prices.forEach { (<#Double#>) in
<#code#>
}
Call the parameter price, and then in the body just print it out.
prices.forEach { (😺price🛑) in
😺print(price)🛑
}
Now go ahead and run the playground to see you get the same results in the console from the for loop and forEach. This closure is already pretty short, but, for practice, let’s write this closure in the shortest form we can.
There’s already no parameter or return type, so the only thing left to do is remove the parameter name and in…
prices.forEach { ❌(price) in❌
and replace the use of that parameter name with a $0.
prices.forEach { print(😺$0🛑) }
Run the playground one more time, just to make sure, and there you go! Same results. So that’s forEach. What about map?
Let’s say we want to have a 10% off sale on whatever all of these prices are for! To get those values, we’d want to multiply each element in the array by .9 to get 90% of the price.
You could do that with a for loop, as well. It would look something like this. Create an empty array.
var arrayForSalePrices: [Double] = []
Loop through all of the prices,
for price in prices {
}
multiply each one by .9, and add the result to the array.
arrayForSalePrices.append(price * 0.9)
Then check the value of arrayForSalePrices
arrayForSalePrices
There’s the prices with 10% chopped off! The map function can help us do exactly the same thing, but with less code!
map takes a closure, executes it on each element in a collection, and returns a new array containing each result. So create a new constant to store those sale prices in…
let salePrices =
and then call map on the prices array
let salePrices = 😺prices.map { (<#Double#>) -> T in
<#code#>
}
For map, the closure you pass in takes one parameter. In this case, it takes a Double! Just like with forEach, the parameter type is decided by the collection you’re calling map on. That means we can just give it a name, and rely on type inference for the type. Let’s call it price.
let salePrices = prices.map { (😺price❌) -> T in
This “T” stands for type, and in our case, it will also be a Double.
let salePrices = prices.map { (price) -> 😺Double❌ in
The return type can be the same as the parameter type, but it doesn’t have to be! We’ll look at an example of returning a different type in a minute.
Inside the closure, return the same calculation as the for loop, “price times 0.9”.
let salePrices = prices.map { price -> Double in
price * 0.9
}
Notice that map maintains the order, just like the for loop. We get exactly the same result.
We can also shorten this closure up! The return type can be inferred from the contents of the closure, and we don’t have to name this parameter. So the entire closure could become “$0 times 0.9”
let salePrices = prices.map { 😺$0 * 0.9🛑 }
Now, maybe we need to get all of these sale prices, nicely formatted, into some labels.
let priceLabels
We can use map, again, to transform this Double array into a String array!
let priceLabels = salePrices.map {...
We’ll write this one in the longer form. Call the parameter price and have it return a String
(price) -> String in
and then try out a String initializer that will let us round all of the prices down to two decimal places.
String(format: "%.2f", price)
Run the playground and on the side you’ll see your sale prices, rounded off to two decimal places, in String form!