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

在iOS中捕获签名时出现性能问题,ios,xamarin.ios,core-graphics,electronic-signature,Ios,Xamarin.ios,Core Graphics,Electronic Signature,我有一个签名控件,它工作得很好,除非你有一个很长的名字,然后它开始显示差距。似乎与性能有关,但在模拟器和最新的iPad上都是一样的。我附上了一个样本项目下面连同签名图纸代码 任何帮助都将不胜感激 }您不能依赖于为每个触摸移动()调用Draw()。如果每2次触摸调用一次Draw(),则会得到如下所述的间隙 我可以通过排队(例如在队列中)来解决这个问题TouchesMoved()中的触摸,然后在Draw()中退出队列 您可能还有另一个问题:每次Draw(),您都在将完整路径添加到当前路径。您可能

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

任何帮助都将不胜感激


}

您不能依赖于为每个
触摸移动()调用
Draw()
。如果每2次触摸调用一次
Draw()
,则会得到如下所述的间隙

我可以通过排队(例如在
队列中)来解决这个问题
TouchesMoved()
中的触摸,然后在
Draw()中退出队列


您可能还有另一个问题:每次
Draw()
,您都在将完整路径添加到当前路径。您可能只需要为新段调用
AddPath
,或者调用
AddPath()
一次,将段添加到路径(`Move,AddLine)并重新绘制它,就可以解决这个问题。但是我还没有测试过这些。在我们的一些内部应用程序用户升级到iOS 7之后,我遇到了完全相同的问题。我确实尝试过使用队列和贝塞尔曲线,而不是连接触点,但最终在我的实现中切换到使用OpenGL

我在这里找到了一个非常有用的指南: 以及Github上的Objective-C项目:

我花了一天时间在C#中重写它,并将其应用到我的应用程序中,因为我在Obj-C中不是很好,但它确实工作得很好


这里提供了类代码(GLSignatureView类):

我遇到了与问题中描述的完全相同的问题。我看了上面@Dmitry的答案,但与我的答案完全不同,需要做很多修改。所以我遵循上面的@Stephane建议,只做了MoveTouchs的排队,效果非常好。谢谢各位

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

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; }
}
使用MonoTouch.CoreGraphics;
使用MonoTouch.UIKit;
使用系统图;
使用制度;
使用Leopard.Interfaces;
使用单调的基础;
使用Leopard.Mobile.Core.Signature;
使用Leopard.Mobile.Core.Drawing;
使用Leopard.Interfaces.Drawing;
使用Leopard.Interfaces.Screens.Controls;
使用System.Linq;
使用System.Collections.Concurrent;
命名空间Leopard.Mobile.Controls
{
公共类SignatureView:LeopardControlBase,ISignatureView
{
公共签名视图(矩形框架):基础(框架)
{
基本框架=框架;
ViewFrame=新LeopardFrame{
X=(int)frame.X,
Y=(int)frame.Y,
宽度=帧。宽度,
高度=框架高度
};
_DrawPath=新的CGPath();
设置外观();
_ScalingFactor=newleopardframe{Width=1,Height=1};
提取水印();
}
public void初始化(int-penWidth、水印设置、水印、字符串backgroundImageFileName)
{
笔宽=笔宽;
水印=水印;
BackgroundImageFileName=BackgroundImageFileName;
变量维度=新LeopardFrame
{
宽度=帧。宽度,
高度=框架高度
};
_SignatureData=新的SignatureData(尺寸、缩放因子、水印);
}
公共空间清除()
{
_DrawPath.Dispose();
_DrawPath=新的CGPath();
_FingerDraw=false;
_TouchLocation=新点F(0,0);
_PrevTouchLocation=新点F(0,0);
SetNeedsDisplay();
_SignatureData.Clear();
提取水印();
_TouchQueue=新的ConcurrentQueue();
}
公共覆盖无效触摸开始(NSSet触摸,UIEVT事件)
{
base.touchesbeated(touchs,evt);
UITouch touch=触摸任何对象作为UITouch;
这个。_FingerDraw=true;
this.\u TouchLocation=touch.LocationInView(this);
this.\u PrevTouchLocation=touch.PreviousLocationInView(this);
this.SetNeedsDisplay();
_SignatureData.AddPoint(signatureEstate.Start,(int)this.\u TouchLocation.X,(int)this.\u TouchLocation.Y);
}
公共覆盖无效触控解除(NSSet触控,UIE事件)
{
基底。触碰附着(触碰,e);
如果(这个)
{
UITouch touch=触摸任何对象作为UITouch;
_TouchLocation=touch.LocationInView(此);
_PreviousLocation=touch.PreviousLocationInView(此);
_FingerDraw=false;
_SignatureData.AddPoint(signatureEstate.End,(int)this.\u TouchLocation.X,(int)this.\u TouchLocation.Y);
}
}
公共覆盖无效触摸移动(NSSet触摸,UIEVT事件)
{
base.TouchesMoved(触摸,evt);
UITouch touch=触摸任何对象作为UITouch;
_TouchLocation=touch.LocationInView(此);
_PreviousLocation=touch.PreviousLocationInView(此);
_Enqueue(新的TouchQueue{TouchLocation=\u TouchLocation,PrevTouchLocation=\u PrevTouchLocation});
_SignatureData.AddPoint(signatureEstate.Move,(int)this.\u TouchLocation.X,(int)this.\u TouchLocation.Y);
SetNeedsDisplay();
}
公共覆盖无效绘制(矩形F矩形)
{
基础绘制(rect);
如果(_DrawPath!=null)
{
使用(CGContext=UIGraphics.GetCurrentContext())
{
if(上下文!=null)
{
图纸签名(上下文);
}
}
}
}
专用void DrawSignatureLines(CGContext上下文)
{
TouchQueue queueElement=null;
while(_touchqueue.TryDequeue(out queueElement))
{
if(queueElement!=null)
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; }
}
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();

    }


}