UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How to use trailing closures and shorthand syntax

Paul Hudson    @twostraws   

Updated for Xcode 15

Swift has a few tricks up its sleeve to reduce the amount of syntax that comes with closures, but first let’s remind ourselves of the problem. Here’s the code we ended up with at the end of the previous chapter:

let team = ["Gloria", "Suzanne", "Piper", "Tiffany", "Tasha"]

let captainFirstTeam = team.sorted(by: { (name1: String, name2: String) -> Bool in
    if name1 == "Suzanne" {
        return true
    } else if name2 == "Suzanne" {
        return false
    }

    return name1 < name2
})

print(captainFirstTeam)

If you remember, sorted() can accept any kind of function to do custom sorting, with one rule: that function must accept two items from the array in question (that’s two strings here), and return a Boolean set to true if the first string should be sorted before the second.

To be clear, the function must behave like that – if it returned nothing, or if it only accepted one string, then Swift would refuse to build our code.

Think it through: in this code, the function we provide to sorted() must provide two strings and return a Boolean, so why do we need to repeat ourselves in our closure?

The answer is: we don’t. We don’t need to specify the types of our two parameters because they must be strings, and we don’t need to specify a return type because it must be a Boolean. So, we can rewrite the code to this:

let captainFirstTeam = team.sorted(by: { name1, name2 in

That’s already reduced the amount of clutter in the code, but we can go a step further: when one function accepts another as its parameter, like sorted() does, Swift allows special syntax called trailing closure syntax. It looks like this:

let captainFirstTeam = team.sorted { name1, name2 in
    if name1 == "Suzanne" {
        return true
    } else if name2 == "Suzanne" {
        return false
    }

    return name1 < name2
}

Rather than passing the closure in as a parameter, we just go ahead and start the closure directly – and in doing so remove (by: from the start, and a closing parenthesis at the end. Hopefully you can now see why the parameter list and in come inside the closure, because if they were outside it would look even weirder!

There’s one last way Swift can make closures less cluttered: Swift can automatically provide parameter names for us, using shorthand syntax. With this syntax we don’t even write name1, name2 in any more, and instead rely on specially named values that Swift provides for us: $0 and $1, for the first and second strings respectively.

Using this syntax our code becomes even shorter:

let captainFirstTeam = team.sorted {
    if $0 == "Suzanne" {
        return true
    } else if $1 == "Suzanne" {
        return false
    }

    return $0 < $1
}

I left this one to last because it’s not as clear cut as the others – some people see that syntax and hate it because it’s less clear, and that’s okay.

Personally I wouldn’t use it here because we’re using each value more than once, but if our sorted() call was simpler – e.g., if we just wanted to do a reverse sort – then I would:

let reverseTeam = team.sorted {
    return $0 > $1
}

So, in is used to mark the end of the parameters and return type – everything after that is the body of the closure itself. There’s a reason for this, and you’ll see it for yourself soon enough.

There I’ve flipped the comparison from < to > so we get a reverse sort, but now that we’re down to a single line of code we can remove the return and get it down to almost nothing:

let reverseTeam = team.sorted { $0 > $1 }

There are no fixed rules about when to use shorthand syntax and when not to, but in case it’s helpful I use shorthand syntax unless any of the following are true:

  1. The closure’s code is long.
  2. $0 and friends are used more than once each.
  3. You get three or more parameters (e.g. $2, $3, etc).

If you’re still unconvinced about the power of closures, let’s take a look at two more examples.

First up, the filter() function lets us run some code on every item in the array, and will send back a new array containing every item that returns true for the function. So, we could find all team players whose name begins with T like this:

let tOnly = team.filter { $0.hasPrefix("T") }
print(tOnly)

That will print ["Tiffany", "Tasha"], because those are the only two team members whose name begins with T.

And second, the map() function lets us transform every item in the array using some code of our choosing, and sends back a new array of all the transformed items:

let uppercaseTeam = team.map { $0.uppercased() }
print(uppercaseTeam)

That will print ["GLORIA", "SUZANNE", "PIPER", "TIFFANY", "TASHA"], because it has uppercased every name and produced a new array from the result.

Tip: When working with map(), the type you return doesn’t have to be the same as the type you started with – you could convert an array of integers to an array of strings, for example.

Like I said, you’re going to be using closures a lot with SwiftUI:

  1. When you create a list of data on the screen, SwiftUI will ask you to provide a function that accepts one item from the list and converts it something it can display on-screen.
  2. When you create a button, SwiftUI will ask you to provide one function to execute when the button is pressed, and another to generate the contents of the button – a picture, or some text, and so on.
  3. Even just putting stacking pieces of text vertically is done using a closure.

Yes, you can create individual functions every time SwiftUI does this, but trust me: you won’t. Closures make this kind of code completely natural, and I think you’ll be amazed at how SwiftUI uses them to produce remarkably simple, clean code.

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.8/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.