Domanda

I have a signature control that works great except if you have a long name then it starts to show gaps. Seems to be performance related, but it is the same both on the simulator and the latest iPad. I have attached a sample project below together with the signature drawing code.

Any help would be very 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;
    }



}

}

È stato utile?

Soluzione 2

After having tried all the answers, I decided to convert an objective-c example using bezier curves that provided a much nicer signature in my opinion. This code is taken from this excellent post on the subject: 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();

    }


}

Altri suggerimenti

You can't rely on Draw() being called for every TouchesMoved(). If Draw() is called every 2 touches, you get gaps like described.

I'd solve that by queuing (e.g. in a Queue<T>) the touches in TouchesMoved() and dequeuing in Draw()

You might also have another issue: at each Draw(), you're re-adding the full path to the current path everytime. You probably can solve this by only calling AddPath for the new segment or calling AddPath() once, adding segments to your path (`Move, AddLine) and redrawing it. But I haven't tested any of this.

I have come across the exact same issue after some of our internal app users have upgraded to iOS 7. I did try using a Queue and bezier curves instead of connecting the touch points, but in the end switched to using OpenGL in my implementation.

I have found a quite useful guide here: Capture a Signature on iOS and the Objective-C project on Github: Signature Demo

It took me a day to rewrite it in C# and adapt it to use in my app as I am not that good in Obj-C, but it does work quite well.

Class code is available here (GLSignatureView class): Github

I have had the exact same issue described in the question. I looked at the answer of @Dmitry above but was quiet different from what I have and it would require lots of changes. so I followed @Stephane advice above and just did the queuing of MoveTouches, that worked perfectly. Thanks guys.

I am putting my solution here in case anybody else would need it. Please notice that I am capturing the signature Points not the signature as an image. We have another algorithem to render 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; }
}

}

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top