While doing iOS programming in Xamarin Studio, I’ve developed a pattern when working with gesture recognizers where I do something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// in the parent view someView.AddGestureRecognizer(CreateSomeParticularRecognizer(someParameter, anotherParameter)); // and then further down: UIPanGestureRecognier CreateSomeParticularRecognizer(someParameter, anotherParameter) { var almostAlwaysSomeLocation = PointF.Empty; return new UIPanGestureRecognizer(nizer => { switch(nizer.State) { // and then } }); } |
i.e. my gesture recognizers almost always work with a factory method that returns a recognizer whose callback closes over some local variables – this way, I have the full power of a dedicated class instance, but without the hazzle of defining a new class every time I need a new kind of recognizer.
I’ve messed around with Swift only a few days now, and I’m beginning to miss my closure-based recognizer pattern… but Swift (or, actually, the way Objective C is bridged) has a problem: When you addTarget on a gesture recognizer, it stores the callback – not as a Swift (UIGestureRecognizer) -> () reference, but rather as an Objective C selector-based target. This makes for a very un-Swifty feeling when adding the target, especially because it forces you to point to a method by typing its name in a string suffixed by “:”.
My first attempt towards supporting my beloved recognizer pattern in Swift was an extension on UIGestureRecognizer that would take a (UIGestureRecognizer) -> () as argument, wrap it in a class instance with an invoke method, and then do the addTarget call with that class instance – but then I got hit by the fact that the recognizer stores its callback targets with weak references, which means that my func-wrapping target would be gone by the time it was supposed to be used.
My current implementation seems to work fine though – I’ve extended UIPanGestureRecognizer to be able to be initialized with a (UIPanGestureRecognizer) -> () as its only argument, which it stores and has invoked by adding the invokeTarget method as the recognizer’s target.
This is the full code for the recognizer:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class UIPanGestureRecognizzle : UIPanGestureRecognizer { var target : (UIPanGestureRecognizer) -> () init(target: (UIPanGestureRecognizer) -> ()) { self.target = target super.init(target: self, action: "invokeTarget:") } func invokeTarget(nizer: UIPanGestureRecognizer!) { target(self) } } |
This way, I can use the recognizer like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// in the parent view someView.addGestureRecognizer(createSomeParticularRecognizer(someParameter, anotherParameter: anotherParameter)) // and then further down func createSomeParticularRecognizer(someParameter : Int, anotherParameter: String) { var almostAlwaysSomeLocation : CGPoint? return UIPanGestureRecognizzle({ (var nizer) -> () in switch nizer.state { // and then do stuff in here } }) } |
which IMO makes for a pretty compact yet powerful way of creating focused and cohesive recognizers without a lot of boilerplate.