Swift iOS 的 UIBezierPath 随着时间的推移绘制性能
Drawing performance over time for a UIBezierPath with Swift for iOS
我正在尝试了解 Swift 中的不同绘图方法以及它们为何如此执行。
下面的代码使用 UIBezierPath
中的项目绘制平滑线,该项目可从 https://github.com/limhowe/LimSignatureView
获得
最初,当用户开始触摸屏幕时,下面代码的性能是高度响应的。然而,随着时间的推移,在屏幕上触摸和移动的时间越长,性能开始滞后,显示的绘图跟不上并且不准确(似乎遗漏了一些点)。一旦触摸屏幕结束并再次开始触摸屏幕以进行新绘图,性能仅 returns 具有高度响应性。
我注意到的事情:
当self.setNeedsDisplay()
被注释掉时(在override func touchesMoved
中),触摸屏幕时没有绘图显示,但是一旦抬起手指触发override func touchesEnded
,最终的结果是完美的绘图,无论持续多长时间都没有滞后或不准确的绘图点。 (这是在绘图时理想地想要和显示的结果。)
当 beizerPath.removeAllPoints()
被注释掉时(在 override func touchesEnded
中),即使在用户抬起手指并再次开始触摸屏幕后,延迟仍然继续发生。
似乎 beizerPath.removeAllPoints()
可能会重置滞后,而 self.setNeedsDisplay()
可能会导致随时间滞后。
当将beizerPath.lineWidth
从2增加到更大的宽度(例如50或100)时,绘图会稍微滞后。
嗯。我很困惑为什么一切都是这样。
这里的时间滞后到底是怎么回事?
beizerPath.removeAllPoints()
和self.setNeedsDisplay()
和滞后有什么关系?
为什么增加 beizerPath.lineWidth
会导致一些轻微的滞后?
还有什么可能导致滞后?
在绘图时删除一些点是否可以提高性能,如果可以,如何实现?
下面的代码需要修改什么地方才能保证完美绘制持续超时不卡顿?
感谢对代码的任何开明反馈和改进。谢谢。
// LimSignatureView.swift
// SwiftSignatureView
//
// Created by MyAdmin on 3/6/15.
// Copyright (c) 2015 MyAdmin. All rights reserved.
//
import UIKit
class LimSignatureView: UIView {
var beizerPath: UIBezierPath = UIBezierPath()
var incrImage : UIImage?
var points : [CGPoint] = Array<CGPoint>(count: 5, repeatedValue: CGPointZero)
var control : Int = 0
var lblSignature : UILabel = UILabel()
var shapeLayer : CAShapeLayer?
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
incrImage?.drawInRect(rect)
beizerPath.stroke()
// Set initial color for drawing
UIColor.redColor().setFill()
UIColor.redColor().setStroke()
beizerPath.stroke()
}
override init(frame: CGRect) {
super.init(frame: frame)
var lblHeight: CGFloat = 61.0
self.backgroundColor = UIColor.blackColor()
beizerPath.lineWidth = 2.0
lblSignature.frame = CGRectMake(0, self.frame.size.height/2 - lblHeight/2, self.frame.size.width, lblHeight);
lblSignature.font = UIFont (name: "HelveticaNeue-UltraLight", size: 30)
lblSignature.text = "Sign Here";
lblSignature.textColor = UIColor.lightGrayColor()
lblSignature.textAlignment = NSTextAlignment.Center
lblSignature.alpha = 0.3;
self.addSubview(lblSignature)
}
// MARK : - TOUCH Implementation
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
if lblSignature.superview != nil {
lblSignature.removeFromSuperview()
}
control = 0;
var touch = touches.anyObject() as UITouch
points[0] = touch.locationInView(self)
var startPoint = points[0];
var endPoint = CGPointMake(startPoint.x + 1.5, startPoint.y
+ 2);
beizerPath.moveToPoint(startPoint)
beizerPath.addLineToPoint(endPoint)
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
var touch = touches.anyObject() as UITouch
var touchPoint = touch.locationInView(self)
control++;
points[control] = touchPoint;
if (control == 4)
{
points[3] = CGPointMake((points[2].x + points[4].x)/2.0, (points[2].y + points[4].y)/2.0);
beizerPath.moveToPoint(points[0])
beizerPath.addCurveToPoint(points[3], controlPoint1: points[1], controlPoint2: points[2])
self.setNeedsDisplay()
points[0] = points[3];
points[1] = points[4];
control = 1;
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
self.drawBitmapImage()
self.setNeedsDisplay()
beizerPath.removeAllPoints()
control = 0
}
override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {
self.touchesEnded(touches, withEvent: event)
}
// MARK : LOGIC
func drawBitmapImage() {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0);
if incrImage != nil {
var rectpath = UIBezierPath(rect: self.bounds)
UIColor.clearColor().setFill()
rectpath.fill()
}
incrImage?.drawAtPoint(CGPointZero)
//Set final color for drawing
UIColor.redColor().setStroke()
beizerPath.stroke()
incrImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
它变得迟钝的原因有两个。
首先,您在触摸事件中进行绘图。这被认为不是一个好主意。我同意这是获得最干净的触摸移动跟踪的最佳方法,但如果您关心性能,那绝对不是一个好主意。
其次,您正在绘制每个移动事件的整个路径(从触摸到向上)。因此,即使您在到达第四段时已经绘制了前三段,您也可以清除屏幕并再次绘制前三段。这与每次触摸事件发生的绘图相结合会导致严重的减速。
理想情况下,您会将最新的触摸事件缓存到一个对象中。然后创建一个计时器(可能是 60 fps?)并从最后一个计时器事件到当前缓存的触摸位置画一条线。这可能会导致线条无法紧密跟随触摸事件,但您可能需要尝试一下。
然后通过该优化,您应该绘制到图像上下文中,然后在需要时将该上下文绘制到屏幕上。这样,您只将最新的段绘制到上下文中,而不是重新绘制整个路径。
这两件事应该会极大地提高你的表现。它肯定会对跟踪触摸事件的清晰度产生不利影响,但你应该能够在某处找到一个快乐的媒介。也许您缓存了所有触摸事件,然后在计时器事件上将所有最新点绘制到上下文中并将该上下文填充到屏幕上。那么你应该能够保持跟踪的清晰度并提高性能。
您还可以研究在屏幕上的 UIImageView 内绘制 UIImage。这可能会保留您的历史绘制路径,而不需要您每次都重新绘制它,但我没有这方面的经验。
我正在尝试了解 Swift 中的不同绘图方法以及它们为何如此执行。
下面的代码使用 UIBezierPath
中的项目绘制平滑线,该项目可从 https://github.com/limhowe/LimSignatureView
最初,当用户开始触摸屏幕时,下面代码的性能是高度响应的。然而,随着时间的推移,在屏幕上触摸和移动的时间越长,性能开始滞后,显示的绘图跟不上并且不准确(似乎遗漏了一些点)。一旦触摸屏幕结束并再次开始触摸屏幕以进行新绘图,性能仅 returns 具有高度响应性。
我注意到的事情:
当
self.setNeedsDisplay()
被注释掉时(在override func touchesMoved
中),触摸屏幕时没有绘图显示,但是一旦抬起手指触发override func touchesEnded
,最终的结果是完美的绘图,无论持续多长时间都没有滞后或不准确的绘图点。 (这是在绘图时理想地想要和显示的结果。)当
beizerPath.removeAllPoints()
被注释掉时(在override func touchesEnded
中),即使在用户抬起手指并再次开始触摸屏幕后,延迟仍然继续发生。似乎
beizerPath.removeAllPoints()
可能会重置滞后,而self.setNeedsDisplay()
可能会导致随时间滞后。当将
beizerPath.lineWidth
从2增加到更大的宽度(例如50或100)时,绘图会稍微滞后。
嗯。我很困惑为什么一切都是这样。
这里的时间滞后到底是怎么回事?
beizerPath.removeAllPoints()
和self.setNeedsDisplay()
和滞后有什么关系?为什么增加
beizerPath.lineWidth
会导致一些轻微的滞后?还有什么可能导致滞后?
在绘图时删除一些点是否可以提高性能,如果可以,如何实现?
下面的代码需要修改什么地方才能保证完美绘制持续超时不卡顿?
感谢对代码的任何开明反馈和改进。谢谢。
// LimSignatureView.swift
// SwiftSignatureView
//
// Created by MyAdmin on 3/6/15.
// Copyright (c) 2015 MyAdmin. All rights reserved.
//
import UIKit
class LimSignatureView: UIView {
var beizerPath: UIBezierPath = UIBezierPath()
var incrImage : UIImage?
var points : [CGPoint] = Array<CGPoint>(count: 5, repeatedValue: CGPointZero)
var control : Int = 0
var lblSignature : UILabel = UILabel()
var shapeLayer : CAShapeLayer?
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
incrImage?.drawInRect(rect)
beizerPath.stroke()
// Set initial color for drawing
UIColor.redColor().setFill()
UIColor.redColor().setStroke()
beizerPath.stroke()
}
override init(frame: CGRect) {
super.init(frame: frame)
var lblHeight: CGFloat = 61.0
self.backgroundColor = UIColor.blackColor()
beizerPath.lineWidth = 2.0
lblSignature.frame = CGRectMake(0, self.frame.size.height/2 - lblHeight/2, self.frame.size.width, lblHeight);
lblSignature.font = UIFont (name: "HelveticaNeue-UltraLight", size: 30)
lblSignature.text = "Sign Here";
lblSignature.textColor = UIColor.lightGrayColor()
lblSignature.textAlignment = NSTextAlignment.Center
lblSignature.alpha = 0.3;
self.addSubview(lblSignature)
}
// MARK : - TOUCH Implementation
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
if lblSignature.superview != nil {
lblSignature.removeFromSuperview()
}
control = 0;
var touch = touches.anyObject() as UITouch
points[0] = touch.locationInView(self)
var startPoint = points[0];
var endPoint = CGPointMake(startPoint.x + 1.5, startPoint.y
+ 2);
beizerPath.moveToPoint(startPoint)
beizerPath.addLineToPoint(endPoint)
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
var touch = touches.anyObject() as UITouch
var touchPoint = touch.locationInView(self)
control++;
points[control] = touchPoint;
if (control == 4)
{
points[3] = CGPointMake((points[2].x + points[4].x)/2.0, (points[2].y + points[4].y)/2.0);
beizerPath.moveToPoint(points[0])
beizerPath.addCurveToPoint(points[3], controlPoint1: points[1], controlPoint2: points[2])
self.setNeedsDisplay()
points[0] = points[3];
points[1] = points[4];
control = 1;
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
self.drawBitmapImage()
self.setNeedsDisplay()
beizerPath.removeAllPoints()
control = 0
}
override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {
self.touchesEnded(touches, withEvent: event)
}
// MARK : LOGIC
func drawBitmapImage() {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0);
if incrImage != nil {
var rectpath = UIBezierPath(rect: self.bounds)
UIColor.clearColor().setFill()
rectpath.fill()
}
incrImage?.drawAtPoint(CGPointZero)
//Set final color for drawing
UIColor.redColor().setStroke()
beizerPath.stroke()
incrImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
它变得迟钝的原因有两个。
首先,您在触摸事件中进行绘图。这被认为不是一个好主意。我同意这是获得最干净的触摸移动跟踪的最佳方法,但如果您关心性能,那绝对不是一个好主意。
其次,您正在绘制每个移动事件的整个路径(从触摸到向上)。因此,即使您在到达第四段时已经绘制了前三段,您也可以清除屏幕并再次绘制前三段。这与每次触摸事件发生的绘图相结合会导致严重的减速。
理想情况下,您会将最新的触摸事件缓存到一个对象中。然后创建一个计时器(可能是 60 fps?)并从最后一个计时器事件到当前缓存的触摸位置画一条线。这可能会导致线条无法紧密跟随触摸事件,但您可能需要尝试一下。
然后通过该优化,您应该绘制到图像上下文中,然后在需要时将该上下文绘制到屏幕上。这样,您只将最新的段绘制到上下文中,而不是重新绘制整个路径。
这两件事应该会极大地提高你的表现。它肯定会对跟踪触摸事件的清晰度产生不利影响,但你应该能够在某处找到一个快乐的媒介。也许您缓存了所有触摸事件,然后在计时器事件上将所有最新点绘制到上下文中并将该上下文填充到屏幕上。那么你应该能够保持跟踪的清晰度并提高性能。
您还可以研究在屏幕上的 UIImageView 内绘制 UIImage。这可能会保留您的历史绘制路径,而不需要您每次都重新绘制它,但我没有这方面的经验。