11

A UIView needs to change a warning label depending on the completion handler of a custom control:

    voucherInputView.completionHandler = {[weak self] (success: Bool) -> Void in

        self?.proceedButton.enabled = success
        self?.warningLabel.alpha = 1.0

        if success
        {
            self?.warningLabel.text = "Code you entered is correct"
            self?.warningLabel.backgroundColor = UIColor.greenColor()
        }
        else
        {
            self?.warningLabel.text = "Code you entered is incorrect"
            self?.warningLabel.backgroundColor = UIColor.orangeColor()
        }


        UIView.animateWithDuration(NSTimeInterval(1.0), animations:{ ()-> Void in
            self?.warningLabel.alpha = 1.0
        })

The final animation block shows an error in the form.

Cannot invoke 'animateWithDuration' with an argument list of type '(NSTimeInterval), animations: ()-> Void)'

If i call this somewhere outside of the completion closure it works.

tshepang
  • 12,111
  • 21
  • 91
  • 136
Ilker Baltaci
  • 11,644
  • 6
  • 63
  • 79

2 Answers2

39

The problem is that the closure is implicitly returning the result of this expression:

self?.warningLabel.alpha = 1.0

but the closure itself is declared as returning Void.

Adding an explicit return should resolve the problem:

UIView.animateWithDuration(NSTimeInterval(1.0), animations: { ()-> Void in
    self?.warningLabel.alpha = 1.0
    return
})
Antonio
  • 71,651
  • 11
  • 148
  • 165
  • 5
    This fixed it for me, but would someone mind to explain *why* this behavior is so weird and unexpected for many people? BTW, in your example you can replace `()->Void` with `_` and append return using `; return` to the same line. ALSO, you can write `; ()` instead of a single-line `return`. :) – BastiBen Dec 17 '14 at 10:41
  • 1
    @badcat When you write a closure without a `return` keyword, the closure automatically returns the last statement. It does this so `array.sort {$0.index < $1.index}` works. If your last statement is an assignment of optional type, it will return `Void?`. You can see this if you let Xcode infer the type: `() -> ()?`. The empty `return` statement implicitly returns `Void` (the non-optional type). Fun fact: `Void` is a `typealias` for `()`, the empty tuple, which all functions return if you don't type a `return` statement. (1/2) – vrwim Apr 23 '15 at 08:56
  • 1
    (2/2) Functions can handle optional `Void` as results from the last line as they don't need to guess any `returns`. All returns must be explicit. – vrwim Apr 23 '15 at 08:56
  • @badcat What is the sweetness in writing `; return` or `; ()` over just `return`? Above all, `; return` has more characters to _type_ than `return`? Or if Antonio can answer, please do. Thanks in advance. – Unheilig Jun 05 '15 at 07:47
  • 1
    @Unheilig: You can write the closure in a single line - in that case the 2 statements must be separated: `self?.warningLabel.alpha = 1.0; return` – Antonio Jun 05 '15 at 08:12
  • @Antonio Now I see it. Already upvoted before your reply. Thanks. – Unheilig Jun 05 '15 at 08:16
0

Antonio's solution also applies with nested closures, like doing an AFNetworking request within UITableViewRowAction handler.

override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]? {

    let cleanRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Do Stuff", handler: {[weak self](action: UITableViewRowAction!, indexPath: NSIndexPath!) in

        AFHTTPSessionManager(baseURL: NSURL(string: "http://baseurl")).PUT("/api/", parameters: nil, success: { (task: NSURLSessionDataTask!, response: AnyObject!) -> Void in

                // Handle success

                self?.endEditing()
                return
            }, failure: { (task: NSURLSessionDataTask!, error: NSError!) -> Void in

                // Handle error

                self?.endEditing()
                return
        })
        return

    })

    cleanRowAction.backgroundColor = UIColor.greenColor()
    return [cleanRowAction]
}