iOS Swift Pass Closure as Property?

Mike picture Mike · Jun 13, 2014 · Viewed 19.1k times · Source

Let's say I have a custom UIView, lets call it MyCustomView. Inside this view is a UITextField property. Suppose my goal is to be able to create an instance of MyCustomView and add it to some view controller somewhere, and I want that view controller to be able to handle actions taken on that text field. For example, if I hit "return" on the keyboard within the text field, I may want to do some action - let me give an example of what I'm envisioning with some objective-c psuedo code:

MyCustomView *myView = [[MyCustomView alloc] initWithFrame:CGRectMake(10,10,100,100)];
myView.textField.actionBlock = { /* do stuff here! */ }
[self.view addSubview:myView];

And then inside the MyCustomView class I would do something like:

- (BOOL)textFieldShouldReturn:(UITextField *)textField  {
    self.actionBlock();
    return NO;
}

I'd like the customView to be the UITextFieldDelegate so that every time I do this I won't have to add all the delegate methods to the view controllers I'm adding it to, but rather have a single implementation that just does whatever I pass to it... How would one go about doing this in swift?

Answer

Mick MacCallum picture Mick MacCallum · Jun 13, 2014

Sure, you can do this. Swift has first class functions, so you are able to do things like directly pass functions around like variables. Keep in mind, functions themselves are in fact closures behind the scenes. Here's a basic example:

class MyClass {
    var theClosure: (() -> ())?

    init() {
        self.theClosure = aMethod
    }

    func aMethod() -> () {
        println("I'm here!!!")
    }
}


let instance = MyClass()
if let theClosure = instance.theClosure {
    theClosure()
}

instance.theClosure = {
    println("Woo!")
}
instance.theClosure!()

And here is the same example using closures that can take a String parameter.

class MyClass {
    var theClosure: ((someString: String) -> ())?

    init() {
        self.theClosure = aMethod
    }

    func aMethod(aString: String) -> () {
        println(aString)
    }
}

let instance = MyClass()
if let theClosure = instance.theClosure {
    theClosure(someString: "I'm the first cool string")
}

instance.theClosure = {(theVerySameString: String) -> () in
    println(theVerySameString)
    someThingReturningBool()
}
instance.theClosure!(someString: "I'm a cool string!")