Few weeks ago I wrote a post about how to handle tap gesture on iOS prior gesture recognizers. In Objective-C there is simple trick with [self.nextResponder …] and then [NSObject cancelPreviousPerformRequestWithTarget …].
In MonoTouch NextResponder on Controller object is read only for some reason, and therefore cannot be used to implement this technique in a simple way. So a bit more coding is needed.
See the custom TapGestureRecognizer below:
public class DateTimeList : List<DateTime> { public UITouch Touch { get; set; } public DateTimeList (UITouch touch) { Touch = touch; } } public class TapGestureRecognizer { Dictionary <string, DateTimeList> touchEndedTimeMap = new Dictionary<string, DateTimeList>(); public delegate void TouchEndedHandler (UITouch touch, int touchCount); public event TouchEndedHandler TouchEnded; public UIView View { get; set; } ////// /// public TapGestureRecognizer (UIView view) { View = view; } ////// /// public void CheckTouchEnded () { { List<string> ptrToRemove = new List<string> (); foreach (string pointLocation in touchEndedTimeMap.Keys) { DateTimeList timeList = touchEndedTimeMap[pointLocation] as DateTimeList; // foreach time there check the current time agains the one there, if there are more // than one in it, close enought to each other // i.e. less than 190ms and current time after the last one is more than 190ms as well // then throw the tap count event for touchEnded // in fact it would be fine to check the last time in the list and fire the event with the // amount of times in the list so we have the tapCount DateTime lastTime = timeList.Last (); TimeSpan span = new TimeSpan (DateTime.Now.Ticks - lastTime.Ticks); if (span.Milliseconds > 190) { ptrToRemove.Add (pointLocation); // fire the event, enter the amount of entries in the list if (TouchEnded != null) TouchEnded (timeList.Touch, timeList.Count); } } lock (touchEndedTimeMap) { foreach (string point in ptrToRemove) touchEndedTimeMap.Remove (point); ptrToRemove.Clear (); } } } ////// /// /// /// A/// public void AddTouchesEnded (NSSet touches) { lock (touchEndedTimeMap) { foreach (UITouch touch in touches.ToArray<UITouch> ()) { PointF location = touch.LocationInView (View); DateTimeList timeList; if (touchEndedTimeMap.ContainsKey(location.ToString())) timeList = touchEndedTimeMap[location.ToString()] as DateTimeList; else { timeList = new DateTimeList (touch); touchEndedTimeMap.Add (location.ToString(), timeList); } timeList.Add (DateTime.Now); Helper.PerformSelector (new DelegateFnc (CheckTouchEnded), 0, 200); } } } }
The code itself uses the Helper class from previous post for the PerformSelector with delay feature. The idea behind this is to actually delegate the touch events to the helper gesture recognizer class and just handle the event handler for TouchEnded event, which will return just the single and prepared UITouch object with the tap count info.
To use the class we need to instantiate it, for example in the viewDidLoad method:
TapGestureRecognizer tapGestureRecognizer = new TapGestureRecognizer(this.View); tapGestureRecognizer.TouchEnded += HandleTapGestureRecognizerTouchEnded;
To handle the gestures:
void HandleTapGestureRecognizerTouchEnded (UITouch touch, int touchCount) { if (touchCount == 1) { // single tap } else if (touchCount == 2) { // double tap } }
And to be complete, we need to delegate the actual events to the recognizer, so in the touchesEnded handler call it:
public override void TouchesEnded (NSSet touches, UIEvent evt) { base.TouchesEnded (touches, evt); tapGestureRecognizer.AddTouchesEnded (touches); }
And thats all, works like a charm.
Happy coding!