我有一个很好用的签名控件,除非你有一个很长的名字然后它开始显示间隙。似乎与性能有关,但在模拟器和最新的iPad上都是一样的。我在下面附上了一个示例项目和签名图纸代码。
非常感谢任何帮助!
https://dl.dropboxusercontent.com/u/25670071/SignatureArchive.zip
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;
}
}
}
答案 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; }
}
}