How to Use Trailing Closure in If Condition

How to use trailing closure in if condition?

You have to put parentheses around the function call:

if "SMITH" == (lastNameForPerson(Person()) {$0.uppercaseString}) {
print("It's bob")
}

Or you put them around the == comparison (around the if condition) in a C-style manner:

if ("SMITH" == lastNameForPerson(Person()) {$0.uppercaseString}) {
print("It's bob")
}

Alternatively, you can move the closure inside the parameter list (though this requires you to explicitly name the parameter):

if "SMITH" == lastNameForPerson(Person(), caseFolding: {$0.uppercaseString}) {
print("It's bob")
}

The reason this problem arises is that the if statement 'claims' the {} block, i.e. it doesn't belong to the lastNameForPerson call any more. For the compiler, the second code block now looks like a normal block that wasn't properly separated from the previous (if) statement.


You should probably consider avoiding a construct like this in general, since it might be hard to read (at first). Instead, you could store the result of the function call in a variable and compare that instead:

let lastName = lastNameForPerson(Person()) {$0.uppercaseString}
if "SMITH" == lastName {
print("It's bob")
}

Guard with trailing closure - Swift Syntax


Or is there a way to somehow have Xcode do autocompletion without the shortened syntax?

Xcode is quite smart. It chooses the non-trailing closure version if you do the auto completion in a control flow statement that needs a {. For example, if I type:

guard let a = reminders.first

The auto complete shows:

Sample Image

If I choose the highlighted option, I get:

guard let a = reminders.first(where: <#T##(String) throws -> Bool#>)

Sample Image

If I then press enter, I get:

guard let a = reminders.first(where: { (<#String#>) -> Bool in
<#code#>
})

Sample Image

It doesn't turn it into a trailing closure, as long as you autocomplete within a guard/if statement.

Implementing Trailing Closure Syntax In A View Modifier That Wraps Another View

Each of your item parameters is defined as (V) -> V -- this means a closure that takes V as a parameter and returns V. In fact, all you want is () -> V (a closure that takes no parameters and returns V). That gets you the trailing closure you want -- then, you can pass just the V to the view modifiers.

@available(iOS 14, *)
struct ToolbarAvailable14ViewModifier<V>: ViewModifier where V: View {
var placement: ToolbarItemPlacement
var item: V

func body(content: Content) -> some View {
content
.toolbar {
ToolbarItemGroup(placement: placement) {
item
}
}
}
}

@available(iOS 15, *)
struct ToolbarAvailable15ViewModifier<V>: ViewModifier where V: View {
var placement: ToolbarItemPlacement
var item: V

func body(content: Content) -> some View {
content
.toolbar {
ToolbarItemGroup(placement: placement) {
item
}
}
}
}

extension View {
@ViewBuilder
func toolbarIfAvailable<V>(placement: ToolbarItemPlacement, _ item: @escaping () -> V) -> some View where V: View {
if #available(iOS 14, *) {
self
.modifier(ToolbarAvailable14ViewModifier(placement: placement, item: item()))
}
if #available(iOS 15, *) {
self
.modifier(ToolbarAvailable15ViewModifier(placement: placement, item: item()))
}
else {
self
}
}
}

This also takes care of your _ in issue since the closure no longer takes an argument/parameter.

Using trailing closure in for-in loop

This is because there would be ambiguity as to the context in which the trailing function should operate. An alternative syntax which works is:

let numbers = [2, 4, 6, 8, 10]

for doubled in (numbers.map { $0 * 2 }) // All good :)
{
print(doubled)
}

I would say this is likely because the 'in' operator has a higher precedence than trailing functions.

It's up to you which you think is more readable.

Super simple trailing closure syntax in Swift

Here is your example fixed:

func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here
print("we do something here and then go back")

// don't forget to call the closure
closure()
}


print("about to call function")

// call the function using trailing closure syntax
someFunctionThatTakesAClosure() {
print("we did what was in the function and can now do something else")
}

print("after calling function")

Output:

about to call function
we do something here and then go back
we did what was in the function and can now do something else
after calling function

Using if-let with trailing closure

If you really want to do the closure inline, I find going back to putting the trailing closure inside the function parentheses is better than wrapping the whole function + closure in parenthesis.

So:

if let newString = myFunction("hello world", { _->() in println("Do nothing") }) {
println(newString)
}

instead of:

if let newString = (myFunction("hello world") { _->() in println("Do nothing") }) {
println(newString)
}

Do nothing is printed just fine if I paste your code into a playground – though only in the timeline (cmd-opt-enter), not in the right-hand margin.

Implicit parameters in trailing closure

In

doMathOperation(4, 5, operation: multiply)

...you are being asked for a function that takes two Double parameters, and multiply is the name of just such a function, so it compiles and works.

In

doMathOperation(4, 5) { ... }

the curly braces themselves are the (body of the) function that takes two Double parameters. It makes no sense to put the name of another such function inside the curly braces.

But of course you are free to call any function you like, passing along the parameters that were passed to you; hence this works:

doMathOperation(4, 5) { multiply($0,$1) }

So, just to sum up: In the first one

doMathOperation(4, 5, operation: multiply)

there is one function, multiply, and 4 and 5 are passed to it. In the second one

doMathOperation(4, 5) { multiply($0,$1) }

there are two functions, "anonymous" (the curly braces) and multiply, and the 4 and 5 are passed into the first one and then it calls the second one.

Trailing closure issue in swift?

As @rob-napier states, you can do it just because the language allows it.

But, note your example is also doing two different things in that last part:

sum(from: 1, to: 10) {
$0
}

Not only are you omitting the return statement, you're also omitting the named parameters, so the $0 is not dependant on the omitting the return feature.

This would be a more accurate example for just omitting return:

sum(from: 1, to: 10) { (num) -> (Int) in
num
}

That said, I wouldn't recommend using either of these features. In most cases, it's better to make the code easier to read later. Your future self (and others who use the code after you) will thank you.

Swift completion handler - escaping trailing closure

To pass a trailing closure you need to end/close your function call and omit the closure argument label. For instance:

func foo(first: Int, second: Int, handler: (Int) -> Void) {
...
}

call syntax:

foo(first: 10, second: 20) { result in
/* trailing closure body */
}

By the way, you should simply your handler argument declaration from:

handler: @escaping (_ imageURL: (ImageURL)) -> ()

to this:

handler: @escaping (ImageURL) -> Void

Using Void or () is matter of style since they are logically the same. I prefer the former ;)



Related Topics



Leave a reply



Submit