22

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?

Mike
  • 9,765
  • 5
  • 34
  • 59

1 Answers1

47

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!")
Mick MacCallum
  • 129,200
  • 40
  • 280
  • 281
  • Excellent, thanks for the info! I'm still grasping unwrapping optionals and such, so I'm wondering what happens if I want the closure to be optional? Using this code, if no closure is provided it crashes. – Mike Jun 13 '14 at 03:06
  • @Mike yeah sorry about that. I've included the unwrapping in my edit. – Mick MacCallum Jun 13 '14 at 03:11
  • Also, another quirk semi-related. Lets say I want to call a function inside the closure that does something and returns a Bool, but I don't care about the result... Seems that I have to do something like this { var unusedResult = self.doSomethingThatAlsoReturnsBool() } otherwise I get "Cannot convert the expressions type '()' to 'Bool'" error. Any way I can avoid creating and assigning the unusedResult? – Mike Jun 13 '14 at 03:11
  • @Mike I'm not sure. I'm unable to reproduce that behavior. – Mick MacCallum Jun 13 '14 at 03:23
  • Create a function like: func testFunction() -> Bool { }, and then try to call it from inside the closure -- instance.theClosure = { self.textFunction() } – Mike Jun 13 '14 at 03:36
  • One last question, if I want to pass a string as a parameter of this closure, how would I add that? Thanks again for your help! – Mike Jun 13 '14 at 04:42
  • 1
    @Mike Sure thing. I've added an example of that to my post. I'm still not sure why exactly you're getting that error though. – Mick MacCallum Jun 13 '14 at 04:46