Swift iOS 的 UIBezierPath 随着时间的推移绘制性能

Drawing performance over time for a UIBezierPath with Swift for iOS

我正在尝试了解 Swift 中的不同绘图方法以及它们为何如此执行。

下面的代码使用 UIBezierPath 中的项目绘制平滑线,该项目可从 https://github.com/limhowe/LimSignatureView

获得

最初,当用户开始触摸屏幕时,下面代码的性能是高度响应的。然而,随着时间的推移,在屏幕上触摸和移动的时间越长,性能开始滞后,显示的绘图跟不上并且不准确(似乎遗漏了一些点)。一旦触摸屏幕结束并再次开始触摸屏幕以进行新绘图,性能仅 returns 具有高度响应性。


我注意到的事情:


嗯。我很困惑为什么一切都是这样。

感谢对代码的任何开明反馈和改进。谢谢。

//  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。这可能会保留您的历史绘制路径,而不需要您每次都重新绘制它,但我没有这方面的经验。