Performance issues when signing a signature in iOS

I have a signature control that works fine if you have a long name then it starts showing spaces. This seems to be related to performance, but it's the same on the simulator and on the latest iPad. Below I will give an example project along with a signature code.

Any help would be greatly appreciated!

https://dl.dropboxusercontent.com/u/25670071/SignatureArchive.zip

enter image description here

using System; using MonoTouch.UIKit; using MonoTouch.CoreGraphics; using System.Drawing; namespace MyApp { public class SignatureViewV2 : UIView { public delegate void SignatureChanged(); public SignatureChanged OnSignatureChanged; private bool _empty = true; // clear the canvas public void Clear () { drawPath.Dispose (); drawPath = new CGPath (); fingerDraw = false; SetNeedsDisplay (); _empty = true; } public bool IsEmpty () { return _empty; } public SignatureViewV2 (RectangleF frame) : base(frame) { this.drawPath = new CGPath (); this.BackgroundColor = UIColor.White; } private PointF touchLocation; private PointF prevTouchLocation; private CGPath drawPath; private bool fingerDraw; public override void TouchesBegan (MonoTouch.Foundation.NSSet touches, UIEvent evt) { base.TouchesBegan (touches, evt); UITouch touch = touches.AnyObject as UITouch; this.fingerDraw = true; this.touchLocation = touch.LocationInView (this); this.prevTouchLocation = touch.PreviousLocationInView (this); this.SetNeedsDisplay (); } public override void Draw (RectangleF rect) { base.Draw (rect); if (this.fingerDraw) { using (CGContext context = UIGraphics.GetCurrentContext()) { context.SetStrokeColor (UIColor.FromRGB(63, 112, 185).CGColor); context.SetLineWidth (2f); context.SetLineJoin (CGLineJoin.Round); context.SetLineCap (CGLineCap.Round); this.drawPath.MoveToPoint (this.prevTouchLocation); this.drawPath.AddLineToPoint (this.touchLocation); context.AddPath (this.drawPath); context.DrawPath (CGPathDrawingMode.Stroke); } if(OnSignatureChanged != null) OnSignatureChanged(); _empty = false; } } public override void TouchesMoved (MonoTouch.Foundation.NSSet touches, UIEvent evt) { base.TouchesMoved (touches, evt); UITouch touch = touches.AnyObject as UITouch; this.touchLocation = touch.LocationInView (this); this.prevTouchLocation = touch.PreviousLocationInView (this); this.SetNeedsDisplay (); } public UIImage GetDrawingImage () { UIImage returnImg = null; UIGraphics.BeginImageContext (this.Bounds.Size); using (CGContext context = UIGraphics.GetCurrentContext()) { context.SetStrokeColor (UIColor.FromRGB(63, 112, 185).CGColor); context.SetLineWidth (5f); context.SetLineJoin (CGLineJoin.Round); context.SetLineCap (CGLineCap.Round); context.AddPath (this.drawPath); context.DrawPath (CGPathDrawingMode.Stroke); returnImg = UIGraphics.GetImageFromCurrentImageContext (); } UIGraphics.EndImageContext (); return returnImg; } } 

}

+4
source share
4 answers

After I tried all the answers, I decided to convert the objective-c example using Bezier curves, which, in my opinion, provided a much nicer signature. This code is taken from this excellent related post: http://mobile.tutsplus.com/tutorials/iphone/ios-sdk_freehand-drawing/

 public class SignatureViewV3 : UIView { public delegate void SignatureChanged (); public SignatureChanged OnSignatureChanged; private bool _empty = true; UIBezierPath path; UIImage incrementalImage; PointF[] pts = new PointF[5]; uint ctr; [Export ("initWithFrame:")] public SignatureViewV3 (RectangleF rect): base(rect) { this.MultipleTouchEnabled = false; this.BackgroundColor = UIColor.Clear; path = new UIBezierPath(); path.LineWidth = 2; } public bool IsEmpty() { return incrementalImage == null && ctr == 0; } public void Clear() { if(incrementalImage != null) { incrementalImage.Dispose (); incrementalImage = null; } path.RemoveAllPoints (); SetNeedsDisplay (); } [Export("initWithCoder:")] public SignatureViewV3 (NSCoder coder) : base(coder) { this.MultipleTouchEnabled = false; this.BackgroundColor = UIColor.Clear; path = new UIBezierPath(); path.LineWidth = 2; } public override void Draw (RectangleF rect) { if (incrementalImage != null) incrementalImage.Draw(rect); path.Stroke(); } public override void TouchesBegan (NSSet touches, UIEvent evt) { ctr = 0; UITouch touch = touches.AnyObject as UITouch; pts[0] = touch.LocationInView(this); } public override void TouchesMoved (NSSet touches, UIEvent evt) { if(OnSignatureChanged != null) OnSignatureChanged (); UITouch touch = touches.AnyObject as UITouch; PointF p = touch.LocationInView(this); ctr++; pts[ctr] = p; if (ctr == 3) { pts[2] = new PointF((pts[1].X + pts[3].X)/2.0f, (pts[1].Y + pts[3].Y)/2.0f); path.MoveTo(pts[0]); path.AddQuadCurveToPoint (pts [2], pts [1]); this.SetNeedsDisplay (); pts[0] = pts[2]; pts[1] = pts[3]; ctr = 1; } } public override void TouchesEnded (NSSet touches, UIEvent evt) { if (ctr == 0) // only one point acquired = user tapped on the screen { path.AddArc (pts [0], path.LineWidth / 2, 0, (float)(Math.PI * 2), true); } else if (ctr == 1) { path.MoveTo (pts [0]); path.AddLineTo (pts [1]); } else if (ctr == 2) { path.MoveTo (pts [0]); path.AddQuadCurveToPoint (pts [2], pts [1]); } this.drawBitmap(); this.SetNeedsDisplay(); path.RemoveAllPoints(); ctr = 0; } public override void TouchesCancelled (NSSet touches, UIEvent evt) { this.TouchesEnded(touches, evt); } public UIImage GetDrawingImage () { UIGraphics.BeginImageContextWithOptions(this.Bounds.Size, false, 0); if(incrementalImage == null) { incrementalImage = new UIImage (); UIBezierPath rectPath = UIBezierPath.FromRect(this.Bounds); UIColor.Clear.SetFill(); rectPath.Fill(); } incrementalImage.Draw(new PointF(0,0)); UIColor.Black.SetStroke(); path.Stroke(); incrementalImage = UIGraphics.GetImageFromCurrentImageContext(); UIGraphics.EndImageContext(); return incrementalImage; } public void drawBitmap() { UIGraphics.BeginImageContextWithOptions(this.Bounds.Size, false, 0); if(incrementalImage == null) { incrementalImage = new UIImage (); UIBezierPath rectPath = UIBezierPath.FromRect(this.Bounds); UIColor.Clear.SetFill(); rectPath.Fill(); } incrementalImage.Draw(new PointF(0,0)); UIColor.Black.SetStroke(); path.Stroke(); incrementalImage = UIGraphics.GetImageFromCurrentImageContext(); UIGraphics.EndImageContext(); } } 
+2
source

You cannot rely on Draw() , which is called for every TouchesMoved() . If Draw() is called every 2 touches, you get spaces as described.

I would solve this by queues (e.g. in Queue<T> ), touch in TouchesMoved() and delete in Draw()

You may have another problem: with each Draw() you re-add the full path to the current path every time. You can probably solve this problem only by calling AddPath for a new segment or by calling AddPath() once, adding segments to your path (`Move, AddLine) and redrawing it. But I did not test anything.

+3
source

I encountered the same problem after some of our internal application users updated iOS 7. I tried to use the Queue and Bezier curves instead of connecting touch points, but eventually switched to using OpenGL in my implementation.

I found a useful guide here: Capturing a signature on iOS and the Objective-C project on Github: Signing a demo

It took me a day to rewrite it in C # and adapt it for use in my application, since I'm not so good in Obj-C, but it works very well.

The class code is available here (GLSignatureView class): Github

+1
source

I had the same problem described in the question. I looked at @Dmitry's answer above, but was completely different from what I have, and that would require a lot of changes. so I followed @Stephane's advice above and just executed the MoveTouches queue, which worked just fine. Thank you, guys.

I put my decision here if someone needs it. Please note that I capture the signature, not the signature as an image. We have another algorithm for rendering these points using different settings.

 using MonoTouch.CoreGraphics; using MonoTouch.UIKit; using System.Drawing; using System; using Leopard.Interfaces; using MonoTouch.Foundation; using Leopard.Mobile.Core.Signature; using Leopard.Mobile.Core.Drawing; using Leopard.Interfaces.Drawing; using Leopard.Interfaces.Screens.Controls; using System.Linq; using System.Collections.Concurrent; namespace Leopard.Mobile.Controls { public class SignatureView : LeopardControlBase, ISignatureView { public SignatureView (RectangleF frame) : base(frame) { base.Frame = frame; ViewFrame = new LeopardFrame { X = (int)frame.X, Y = (int) frame.Y, Width = frame.Width, Height = frame.Height }; _DrawPath = new CGPath(); SetupAppearance(); _ScalingFactor = new LeopardFrame { Width = 1, Height = 1 }; DrawWatermarks(); } public void Initialise(int penWidth, WatermarkSettings watermarks, string backgroundImageFileName) { PenWidth = penWidth; Watermarks = watermarks; BackgroundImageFileName = backgroundImageFileName; var dimensions = new LeopardFrame { Width = Frame.Width, Height = Frame.Height }; _SignatureData = new SignatureData(dimensions, _ScalingFactor, watermarks); } public void Clear () { _DrawPath.Dispose(); _DrawPath = new CGPath(); _FingerDraw = false; _TouchLocation = new PointF(0, 0); _PrevTouchLocation = new PointF(0, 0); SetNeedsDisplay(); _SignatureData.Clear(); DrawWatermarks(); _TouchsQueue = new ConcurrentQueue<TouchsQueue>(); } public override void TouchesBegan(NSSet touches, UIEvent evt) { base.TouchesBegan (touches, evt); UITouch touch = touches.AnyObject as UITouch; this._FingerDraw = true; this._TouchLocation = touch.LocationInView (this); this._PrevTouchLocation = touch.PreviousLocationInView (this); this.SetNeedsDisplay (); _SignatureData.AddPoint(SignatureState.Start, (int)this._TouchLocation.X, (int)this._TouchLocation.Y); } public override void TouchesEnded(NSSet touches, UIEvent e) { base.TouchesEnded(touches, e); if (this._FingerDraw) { UITouch touch = touches.AnyObject as UITouch; _TouchLocation = touch.LocationInView(this); _PrevTouchLocation = touch.PreviousLocationInView(this); _FingerDraw = false; _SignatureData.AddPoint(SignatureState.End, (int)this._TouchLocation.X, (int)this._TouchLocation.Y); } } public override void TouchesMoved (NSSet touches, UIEvent evt) { base.TouchesMoved (touches, evt); UITouch touch = touches.AnyObject as UITouch; _TouchLocation = touch.LocationInView(this); _PrevTouchLocation = touch.PreviousLocationInView(this); _TouchsQueue.Enqueue(new TouchsQueue {TouchLocation = _TouchLocation, PrevTouchLocation = _PrevTouchLocation }); _SignatureData.AddPoint(SignatureState.Move, (int)this._TouchLocation.X, (int)this._TouchLocation.Y); SetNeedsDisplay(); } public override void Draw (RectangleF rect) { base.Draw (rect); if (_DrawPath != null) { using (CGContext context = UIGraphics.GetCurrentContext()) { if (context != null) { DrawSignatureLines(context); } } } } private void DrawSignatureLines(CGContext context) { TouchsQueue queueElement = null; while(_TouchsQueue.TryDequeue(out queueElement)) { if (queueElement != null) { context.SetStrokeColor(UIColor.Black.CGColor); context.SetLineWidth(PenWidth); context.SetLineJoin(CGLineJoin.Round); context.SetLineCap(CGLineCap.Round); _DrawPath.MoveToPoint(queueElement.PrevTouchLocation); _DrawPath.AddLineToPoint(queueElement.TouchLocation); context.AddPath(_DrawPath); context.DrawPath(CGPathDrawingMode.Stroke); } } } public void Add(IControl control) { var view = control as UIView; if (view != null) { EnsureAddingWatermarkControl(view); } } public string GetSignatureData() { var result = string.Empty; if (_SignatureData != null) { try { result = _SignatureData.ExtractAsString(); } catch (Exception exception) { OnFailedWithException(exception); } } return result; } #region Implementation private PointF _TouchLocation; private PointF _PrevTouchLocation; private CGPath _DrawPath; private bool _FingerDraw; private ConcurrentQueue<TouchsQueue> _TouchsQueue = new ConcurrentQueue<TouchsQueue>(); private ILeopardFrame _ScalingFactor; private SignatureData _SignatureData { get; set; } public SignatureData SignatureData { get { return _SignatureData; } } public event SignatureFailedWithExceptionHandler SignatureFailedWithException; public string BackgroundImageFileName {get;set;} public int PenWidth { get; set; } public WatermarkSettings Watermarks {get;set;} public ILeopardFrame ViewFrame { get; set; } private void OnFailedWithException(Exception exception) { if (SignatureFailedWithException != null) { SignatureFailedWithException(exception); } } private void EnsureAddingWatermarkControl(UIView view) { var existingView = this.Subviews.ToList().FirstOrDefault( v => v is IControl && v.Frame.X == view.Frame.X && v.Frame.Y == view.Frame.Y); if (existingView != null) { existingView.RemoveFromSuperview(); existingView.Dispose(); } this.AddSubview(view); } private void DrawWatermarks() { if (Watermarks != null) { Watermarks.DrawWatermarks(this, _ScalingFactor); } } private void SetupAppearance () { BackgroundColor = UIColor.White; Layer.BorderWidth = 5f; Layer.BorderColor = UIColor.FromRGB ( Constants.LeopardBackgroundColors.Red, Constants.LeopardBackgroundColors.Green, Constants.LeopardBackgroundColors.Blue ).CGColor; } #endregion } public class TouchsQueue { public PointF TouchLocation {get;set;} public PointF PrevTouchLocation { get; set; } } 

}

0
source

All Articles