在iOS中捕获签名时的性能问题

时间:2013-09-24 11:03:38

标签: ios xamarin.ios core-graphics electronic-signature

我有一个很好用的签名控件,除非你有一个很长的名字然后它开始显示间隙。似乎与性能有关,但在模拟器和最新的iPad上都是一样的。我在下面附上了一个示例项目和签名图纸代码。

非常感谢任何帮助!

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 个答案:

答案 0 :(得分:3)

您不能依赖Draw()为每个TouchesMoved()调用{}。如果每2次触摸调用Draw(),则会出现类似描述的空白。

我通过排队(例如在Queue<T>中)TouchesMoved()中的触摸并在Draw()中出列

来解决这个问题

您可能还有另一个问题:在每个Draw(),您每次都会重新添加当前路径的完整路径。您可以通过仅为新段调用AddPath或调用AddPath()一次,向您的路径添加段(“Move,AddLine)并重新绘制它来解决此问题。但我没有测试过这些。

答案 1 :(得分:2)

在尝试了所有答案之后,我决定使用bezier曲线转换一个Objective-c示例,在我看来这提供了更好的签名。此代码取自此主题的优秀帖子: 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 :(得分:1)

在我们的一些内部应用用户升级到iOS 7后,我遇到了完全相同的问题。我确实尝试使用Queue和bezier曲线而不是连接触摸点,但最后切换到使用我的OpenGL实施

我在这里找到了一个非常有用的指南:Capture a Signature on iOS 和Github上的Objective-C项目:Signature Demo

我花了一天时间用C#重写它并将其调整为在我的应用程序中使用,因为我在Obj-C中不是很好,但它确实运行得很好。

此处提供类代码(GLSignatureView类):Github

答案 3 :(得分:0)

我遇到了问题中描述的完全相同的问题。我看了上面的@Dmitry的答案,但与我所拥有的安静不同,它需要进行大量的更改。所以我按照上面的@Stephane建议进行操作,只是完成了MoveTouches的排队。多谢你们。

我在这里提出我的解决方案以防其他人需要它。请注意,我正在捕获签名点而不是签名作为图像。我们有另一个算法来使用不同的设置来渲染这些点

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; }
}

}