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