James Webster
James Webster

Reputation: 32066

Why doesn't this closure have the arguments I expect?

I have a SettingsViewController and have created a struct to define the information required for each setting.

///Represents a list of the options needed to populate a `SettingsTableViewCell`
public struct Setting {
    ///The image to use for the icon
    var imageName : String

    ///The text to display as the title of the setting
    var title : String

    ///Called when the switch is tapped. If no closure is supplied, the switch is hidden
    var switchCallback: ((_ status: Bool)->())?
}

The view controller keeps an array of these settings to later be used in a table view. An example of one is provided below:

let options : [Setting] =
[
    Setting(imageName: "notifications", title: "Bump Notifications") {updateNotificationSetting($0)},
    ...
]

However, when I try to compile, I'm presented with the error:

Cannot convert value of type '(SettingsViewController) -> (Bool) -> ()' to expected argument type '((Bool) -> ())?'

Can somebody explain where the (SettingsViewController) is coming from, please? And if you can, what I need to change to fix it, please?


For an SSCCE, see below:

import UIKit

///Represents a list of the options needed to populate a `SettingsTableViewCell`
public struct Setting {
    ///The image to use for the icon
    var imageName : String

    ///The text to display as the title of the setting
    var title : String

    ///Called when the switch is tapped. If no closure is supplied, the switch is hidden
    var switchCallback: ((_ status: Bool)->())?
}



@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    let options : [Setting] =
    [
        //Error is on the following line
        Setting(imageName: "notifications", title: "Bump Notifications") {isOn in updateSetting(isOn)},
    ]

    func updateSetting(isOn : Bool) {

    }

}

Upvotes: 0

Views: 71

Answers (2)

Sulthan
Sulthan

Reputation: 130082

The closure in your Setting initializer is capturing self. However, self in Swift is available only after all the properties of an object are initialized, that is, after let options is initialized.

One method to break the circle is to use lazy initialization of properties:

public struct Setting {
    var imageName : String
    var title : String
    var switchCallback: ((_ status: Bool)->())?
}

class MyClass {
    lazy var options: [Setting] = [
        Setting(imageName: "x", title: "X") { [unowned self] in self.updateSetting(isOn: $0)}
    ]

    func updateSetting(isOn : Bool) {}
}

Note the explicit type : [Setting] is currently required for the declaration.

Note you need to use [unowned self] or [weak self] to break the release cycle (thanks @rob for the comment).

Upvotes: 2

James Webster
James Webster

Reputation: 32066

But (a) you have to have an explicit reference to self in the closure (e.g. self.updateNotificationSetting); and (b) to do that, you can only do that if you make it lazy var rather than let (allowing it to now resolve self).

Although I wasn't able to get this working with the keyword lazy var, this did give me the information I needed to solve the problem. I ended up essentially making my own lazy var, only instantiating it just before I use the array:

private var options : [Setting]!


...

if options == nil {
        options =  [
            Setting(imageName: "notifications", title: "Bump Notifications") {isOn in self.updateNotificationSetting(isOn: isOn)},
            Setting(imageName: "automatic_check_in", title: "Automatic Check In") {isOn in self.updateAutomaticCheckInSetting(isOn:isOn)},
            Setting(imageName: "logout", title: "Logout", switchCallback:nil)
        ]
    }

Upvotes: 0

Related Questions