绘图类绘制直线而不是曲线 [英] Drawing class drawing straight lines instead of curved lines

查看:117
本文介绍了绘图类绘制直线而不是曲线的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下使用UIBezierPath绘制线条的代码。



代码使用 addCurveToPoint 这应该绘制弯曲使用立方贝塞尔路径的行,但代码的最终结果是绘制连接的直线,但 addLineToPoint 未被使用。



可能会发生什么,为什么代码不绘制曲线?



  import UIKit 

class DrawingView:UIView,UITextFieldDelegate {

//代码中的可修改值
let lineWidth:CGFloat = 2.0
let lineColor = UIColor.redColor()
let lineColorAlpha:CGFloat = 0.4
let shouldAllowUserChangeLineWidth = true
let maximumUndoRedoChances = 10

var path = UIBezierPath()

var previousImages:[UIImage] = [UIImage]()

//代表当前图像索引
var currentImageIndex = 0

//平滑绘制曲线的控制点
private var controlPoint1:CGPoint?
private var controlPoint2:CGPoint?

private var undoButton:UIButton!
private var redoButton:UIButton!

private var textField:UITextField!

// MARK:初始化方法
覆盖init(帧:CGRect){
super.init(frame:frame)
setDefaultValues()
}

必需init?(编码器aDecoder:NSCoder){
super.init(编码器:aDecoder)
setDefaultValues()
}

//在需要时绘制路径
覆盖func drawRect(rect:CGRect){
if currentImageIndex> 0 {
previousImages [currentImageIndex - 1] .drawInRect(rect)
}

lineColor.setStroke()
path.strokeWithBlendMode(CGBlendMode.Normal,alpha:lineColorAlpha )
}

覆盖func layoutSubviews(){
super.layoutSubviews()

redoButton.frame = CGRectMake(bounds.size.width - 58 ,30,50,44)
if shouldAllowUserChangeLineWidth {
textField.center = CGPointMake(center.x,52)
}
}

func setDefaultValues (){
multipleTouchEnabled = false
backgroundColor = UIColor.whiteColor()
path.lineWidth = lineWidth

addButtonsAndField()
}

func addButtonsAndField(){
undoButton = UIButton(frame:CGRectMake(8,30,50,44))
undoButton.setTitle(Undo,forState:UIControlState.Normal)
undoButton.setTitleColor(UIColor.blackColor(),forState:UIControlState.Normal)
undoButton.backgroundColor = UIColor.lightGr ayColor()
undoButton.addTarget(self,action:undoButtonTapped:,forControlEvents:UIControlEvents.TouchUpInside)
addSubview(undoButton)

redoButton = UIButton(frame:CGRectMake( bounds.size.width - 58,30,50,44))
redoButton.setTitle(Redo,forState:UIControlState.Normal)
redoButton.setTitleColor(UIColor.blackColor(),forState:UIControlState .Normal)
redoButton.backgroundColor = UIColor.lightGrayColor()
redoButton.addTarget(self,action:redoButtonTapped:,forControlEvents:UIControlEvents.TouchUpInside)
addSubview(redoButton)

if shouldAllowUserChangeLineWidth {
textField = UITextField(frame:CGRectMake(0,0,50,40))
textField.backgroundColor = UIColor.lightGrayColor()
textField.center = CGPointMake(center.x,52)
textField.keyboardType = UIKeyboardType.NumberPad
textField.delegate = self
addSubview(textField)
}
}

// MARK:触摸方法
覆盖func touchesBegan(触摸:设置< UITouch>,withEvent事件:UIEvent?){
//找到起点并在那里移动路径
endEditing(true)

let touchPoint = touches.first?.locationInView(self)

path.moveToPoint(touchPoint !)
}

覆盖func touchesMoved(触摸:设置< UITouch>,withEvent事件:UIEvent?){
让touchPoint = touches.first?.locationInView(self)
controlPoint1 = CGPointMake((path.currentPoint.x + touchPoint!.x)/ 2,(path.currentPoint.y + touchPoint!.y)/ 2)
controlPoint2 = CGPointMake((path.currentPoint。 x + touchPoint!.x)/ 2,(path.currentPoint.y + touchPoint!.y)/ 2)

path.addCurveToPoint(touchPoint!,controlPoint1:controlPoint1!,controlPoint2:controlPoint2!)
setNeedsDisplay()
}

覆盖func touchesEnded(触摸:设置< UITouch>,withEvent事件:UIEvent?){
le t touchPoint = touches.first?.locationInView(self)
controlPoint1 = CGPointMake((path.currentPoint.x + touchPoint!.x)/ 2,(path.currentPoint.y + touchPoint!.y)/ 2)
controlPoint2 = CGPointMake((path.currentPoint.x + touchPoint!.x)/ 2,(path.currentPoint.y + touchPoint!.y)/ 2)

path.addCurveToPoint( touchPoint!,controlPoint1:controlPoint1!,controlPoint2:controlPoint2!)

savePreviousImage()
setNeedsDisplay()

//删除所有点以优化绘图速度
path.removeAllPoints()
}

覆盖func touchesCancelled(触摸:设置< UITouch>?,withEvent事件:UIEvent?){
touchesEnded(touches!,withEvent) :event)
}

// MARK:选择器方法
func undoButtonTapped(sender:UIButton){
if currentImageIndex> 0 {

setNeedsDisplay()

currentImageIndex--
}
}

func redoButtonTapped(sender:UIButton){
if currentImageIndex!= previousImages.count {

setNeedsDisplay()

currentImageIndex ++
}
}

// MARK:UITextFieldDelegate
func textFieldDidEndEditing(textField:UITextField){
if let n = NSNumberFormatter()。numberFromString(textField.text!){
if n.integerValue> 0 {
path.lineWidth = CGFloat(n)
}
}
}

// MARK:保存图像以便在调用undo或redo时重新加载
private func savePreviousImage(){
UIGraphicsBeginImageContextWithOptions(bounds.size,true,UIScreen.mainScreen()。scale)
lineColor.setStroke()

//创建一个白色的图像
让rectPath = UIBezierPath(rect:bounds)
backgroundColor?.setFill()
rectPath.fill()

if currentImageIndex> 0 {
previousImages [currentImageIndex - 1] .drawInRect(bounds)
}

path.strokeWithBlendMode(CGBlendMode.Normal,alpha:lineColorAlpha)

if previousImages.count> = currentImageIndex {
previousImages.removeRange(currentImageIndex ..< previousImages.count)
}

if previousImages.count> = maximumUndoRedoChances {
previousImages.removeFirst()
}
else {
currentImageIndex ++
}

previousImages.append(UIGraphicsGetImageFromCurrentImageContext())
UIGraphicsEndImageContext( )
}
}


解决方案

有一些问题:


  1. 您正在使用两点之间中点的控制点,从而产生线段。您可能希望选择平滑曲线的控制点。请参见


  2. 如果定位iOS 9及更高版本,它会引入一些不错的功能,特别是:




    • 如果用户正在使用具有此功能的设备(尤其是较新的iPad),则会合并触摸。最重要的是,这些设备(但不是它们的模拟器)能够每秒产生超过60次触摸,因此您可以为每次调用报告多次触摸 touchesMoved


    • 预测触摸,设备可以向您显示预期用户触摸的位置将会进展(导致绘图中的延迟减少)。




    将它们拉到一起,您可以执行以下操作:

      var points:[CGPoint]? 
    var path:UIBezierPath?

    覆盖func touchesBegan(_ touches:Set< UITouch>,with event:UIEvent?){
    if let touch = touches.first {
    points = [touch.location( in:view)]
    }
    }

    覆盖func touchesMoved(_ touches:Set< UITouch>,with event:UIEvent?){
    if let touch = touches.first {
    if #available(iOS 9.0,*){
    if let coalescedTouches = event?.coalescedTouches(for:touch){
    points? + = coalescedTouches.map {$ 0.location(in:view)}
    } else {
    points?.append(touch.location(in:view))
    }

    if letTouches = event?.predictedTouches(for:touch){
    let predictPoints = predictTouches.map {$ 0.location(in:view)}
    pathLayer.path = UIBezierPath(catmullRomInterpolatedPoints:points !+ predictPoints,关闭:false,alpha:0.5)?。cgPath
    } else {
    pathLayer.path = UIBezierPath(catmullRomInterpolatedPoints:points!,closed:false,alpha:0.5)?. cgPath
    }
    } else {
    points?.append(touch.location(in:view))
    pathLayer.path = UIBezierPath(catmullRomInterpolatedPoints:points !, closed:false,alpha:0.5 )?。cgPath
    }
    }
    }

    覆盖func touchesEnded(_ touches:Set< UITouch>,with event:UIEvent?){
    path = UIBezierPa th(catmullRomInterpolatedPoints:points!,closed:false,alpha:0.5)
    pathLayer.path = path?.cgPath
    }

    在这段代码中,我通过更新 CAShapeLayer 来渲染路径,但是如果你想以其他方式渲染它,随意。例如,使用 drawRect 方法,您将更新路径,然后调用 setNeedsDisplay ()



    并且,上面说明如果#available(iOS 9,*){... } else {...} 语法如果您需要支持9.0之前的iOS版本,但显然,如果您只支持iOS 9及更高版本,则可以删除该检查并丢失 else 子句。



    有关详细信息,请参阅WWDC 2015视频



    (对于上面的Swift 2.3演绎,请参阅此答案的以前的版本 。)


    I have the below code that draws lines using UIBezierPath.

    The code uses addCurveToPoint which should draw curved lines using a cubic bezier path, however the end result of the code is instead drawing connected straight lines but addLineToPoint isn't being used.

    What could be going on, why isn't the code drawing curved lines?

    import UIKit
    
    class DrawingView: UIView, UITextFieldDelegate {
    
    // Modifiable values within the code
    let lineWidth : CGFloat = 2.0
    let lineColor = UIColor.redColor()
    let lineColorAlpha : CGFloat = 0.4
    let shouldAllowUserChangeLineWidth = true
    let maximumUndoRedoChances = 10
    
    var path = UIBezierPath()
    
    var previousImages : [UIImage] = [UIImage]()
    
    // Represents current image index
    var currentImageIndex = 0
    
    // Control points for drawing curve smoothly
    private var controlPoint1 : CGPoint?
    private var controlPoint2 : CGPoint?
    
    private var undoButton : UIButton!
    private var redoButton : UIButton!
    
    private var textField : UITextField!
    
    //MARK: Init methods
    override init(frame: CGRect) {
        super.init(frame: frame)
        setDefaultValues()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setDefaultValues()
    }
    
    // Draw the path when needed
    override func drawRect(rect: CGRect) {
        if currentImageIndex > 0 {
            previousImages[currentImageIndex - 1].drawInRect(rect)
        }
    
        lineColor.setStroke()
        path.strokeWithBlendMode(CGBlendMode.Normal, alpha: lineColorAlpha)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
    
        redoButton.frame = CGRectMake(bounds.size.width - 58, 30, 50, 44)
        if shouldAllowUserChangeLineWidth {
            textField.center = CGPointMake(center.x, 52)
        }
    }
    
    func setDefaultValues() {
        multipleTouchEnabled = false
        backgroundColor = UIColor.whiteColor()
        path.lineWidth = lineWidth
    
        addButtonsAndField()
    }
    
    func addButtonsAndField() {
        undoButton = UIButton(frame: CGRectMake(8, 30, 50, 44))
        undoButton.setTitle("Undo", forState: UIControlState.Normal)
        undoButton.setTitleColor(UIColor.blackColor(), forState: UIControlState.Normal)
        undoButton.backgroundColor = UIColor.lightGrayColor()
        undoButton.addTarget(self, action: "undoButtonTapped:", forControlEvents: UIControlEvents.TouchUpInside)
        addSubview(undoButton)
    
        redoButton = UIButton(frame: CGRectMake(bounds.size.width - 58, 30, 50, 44))
        redoButton.setTitle("Redo", forState: UIControlState.Normal)
        redoButton.setTitleColor(UIColor.blackColor(), forState: UIControlState.Normal)
        redoButton.backgroundColor = UIColor.lightGrayColor()
        redoButton.addTarget(self, action: "redoButtonTapped:", forControlEvents: UIControlEvents.TouchUpInside)
        addSubview(redoButton)
    
        if shouldAllowUserChangeLineWidth {
            textField = UITextField(frame: CGRectMake(0, 0, 50, 40))
            textField.backgroundColor = UIColor.lightGrayColor()
            textField.center = CGPointMake(center.x, 52)
            textField.keyboardType = UIKeyboardType.NumberPad
            textField.delegate = self
            addSubview(textField)
        }
    }
    
    //MARK: Touches methods
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        // Find the start point and move the path there
        endEditing(true)
    
        let touchPoint = touches.first?.locationInView(self)
    
        path.moveToPoint(touchPoint!)
    }
    
    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let touchPoint = touches.first?.locationInView(self)
        controlPoint1 = CGPointMake((path.currentPoint.x + touchPoint!.x) / 2, (path.currentPoint.y + touchPoint!.y) / 2)
        controlPoint2 = CGPointMake((path.currentPoint.x + touchPoint!.x) / 2, (path.currentPoint.y + touchPoint!.y) / 2)
    
        path.addCurveToPoint(touchPoint!, controlPoint1: controlPoint1!, controlPoint2: controlPoint2!)
        setNeedsDisplay()
    }
    
    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let touchPoint = touches.first?.locationInView(self)
        controlPoint1 = CGPointMake((path.currentPoint.x + touchPoint!.x) / 2, (path.currentPoint.y + touchPoint!.y) / 2)
        controlPoint2 = CGPointMake((path.currentPoint.x + touchPoint!.x) / 2, (path.currentPoint.y + touchPoint!.y) / 2)
    
        path.addCurveToPoint(touchPoint!, controlPoint1: controlPoint1!, controlPoint2: controlPoint2!)
    
        savePreviousImage()
        setNeedsDisplay()
    
        // Remove all points to optimize the drawing speed
        path.removeAllPoints()
    }
    
    override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
        touchesEnded(touches!, withEvent: event)
    }
    
    //MARK: Selector methods
    func undoButtonTapped(sender : UIButton) {
        if currentImageIndex > 0 {
    
            setNeedsDisplay()
    
            currentImageIndex--
        }
    }
    
    func redoButtonTapped(sender : UIButton) {
        if currentImageIndex != previousImages.count {
    
            setNeedsDisplay()
    
            currentImageIndex++
        }
    }
    
    //MARK: UITextFieldDelegate
    func textFieldDidEndEditing(textField: UITextField) {
        if let n = NSNumberFormatter().numberFromString(textField.text!) {
            if n.integerValue > 0 {
                path.lineWidth = CGFloat(n)
            }
        }
    }
    
    //MARK: Saving images for reloading when undo or redo called
    private func savePreviousImage() {
        UIGraphicsBeginImageContextWithOptions(bounds.size, true, UIScreen.mainScreen().scale)
        lineColor.setStroke()
    
        // Create a image with white color
        let rectPath = UIBezierPath(rect: bounds)
        backgroundColor?.setFill()
        rectPath.fill()
    
        if currentImageIndex > 0 {
            previousImages[currentImageIndex - 1].drawInRect(bounds)
        }
    
        path.strokeWithBlendMode(CGBlendMode.Normal, alpha: lineColorAlpha)
    
        if previousImages.count >= currentImageIndex {
            previousImages.removeRange(currentImageIndex..<previousImages.count)
        }
    
        if previousImages.count >= maximumUndoRedoChances {
            previousImages.removeFirst()
        }
        else {
            currentImageIndex++
        }
    
        previousImages.append(UIGraphicsGetImageFromCurrentImageContext())
        UIGraphicsEndImageContext()
    }
    }
    

    解决方案

    There are a few issues:

    1. You are using control points that are midpoints between the two points, resulting in line segments. You probably want to choose control points that smooth the curve. See http://spin.atomicobject.com/2014/05/28/ios-interpolating-points/.

      Here is a Swift 3 implementation of a simple smoothing algorithm, as well as Swift renditions of the above Hermite and Catmull-Rom Spline approaches:

      extension UIBezierPath {
      
          /// Simple smoothing algorithm
          ///
          /// This iterates through the points in the array, drawing cubic bezier
          /// from the first to the fourth points, using the second and third as
          /// control points.
          ///
          /// This takes every third point and moves it so that it is exactly inbetween
          /// the points before and after it, which ensures that there is no discontinuity
          /// in the first derivative as you join these cubic beziers together.
          ///
          /// Note, if, at the end, there are not enough points for a cubic bezier, it
          /// will perform a quadratic bezier, or if not enough points for that, a line.
          ///
          /// - parameter points: The array of `CGPoint`.
      
          convenience init?(simpleSmooth points: [CGPoint]) {
              guard points.count > 1 else { return nil }
      
              self.init()
      
              move(to: points[0])
      
              var index = 0
      
              while index < (points.count - 1) {
                  switch (points.count - index) {
                  case 2:
                      index += 1
                      addLine(to: points[index])
                  case 3:
                      index += 2
                      addQuadCurve(to: points[index], controlPoint: points[index-1])
                  case 4:
                      index += 3
                      addCurve(to: points[index], controlPoint1: points[index-2], controlPoint2: points[index-1])
                  default:
                      index += 3
                      let point = CGPoint(x: (points[index-1].x + points[index+1].x) / 2,
                                          y: (points[index-1].y + points[index+1].y) / 2)
                      addCurve(to: point, controlPoint1: points[index-2], controlPoint2: points[index-1])
                  }
              }
          }
      
          /// Create smooth UIBezierPath using Hermite Spline
          ///
          /// This requires at least two points.
          ///
          /// Adapted from https://github.com/jnfisher/ios-curve-interpolation
          /// See http://spin.atomicobject.com/2014/05/28/ios-interpolating-points/
          ///
          /// - parameter hermiteInterpolatedPoints: The array of CGPoint values.
          /// - parameter closed:                    Whether the path should be closed or not
          ///
          /// - returns:  An initialized `UIBezierPath`, or `nil` if an object could not be created for some reason (e.g. not enough points).
      
          convenience init?(hermiteInterpolatedPoints points: [CGPoint], closed: Bool) {
              self.init()
      
              guard points.count > 1 else { return nil }
      
              let numberOfCurves = closed ? points.count : points.count - 1
      
              var previousPoint: CGPoint? = closed ? points.last : nil
              var currentPoint:  CGPoint  = points[0]
              var nextPoint:     CGPoint? = points[1]
      
              move(to: currentPoint)
      
              for index in 0 ..< numberOfCurves {
                  let endPt = nextPoint!
      
                  var mx: CGFloat
                  var my: CGFloat
      
                  if previousPoint != nil {
                      mx = (nextPoint!.x - currentPoint.x) * 0.5 + (currentPoint.x - previousPoint!.x)*0.5
                      my = (nextPoint!.y - currentPoint.y) * 0.5 + (currentPoint.y - previousPoint!.y)*0.5
                  } else {
                      mx = (nextPoint!.x - currentPoint.x) * 0.5
                      my = (nextPoint!.y - currentPoint.y) * 0.5
                  }
      
                  let ctrlPt1 = CGPoint(x: currentPoint.x + mx / 3.0, y: currentPoint.y + my / 3.0)
      
                  previousPoint = currentPoint
                  currentPoint = nextPoint!
                  let nextIndex = index + 2
                  if closed {
                      nextPoint = points[nextIndex % points.count]
                  } else {
                      nextPoint = nextIndex < points.count ? points[nextIndex % points.count] : nil
                  }
      
                  if nextPoint != nil {
                      mx = (nextPoint!.x - currentPoint.x) * 0.5 + (currentPoint.x - previousPoint!.x) * 0.5
                      my = (nextPoint!.y - currentPoint.y) * 0.5 + (currentPoint.y - previousPoint!.y) * 0.5
                  }
                  else {
                      mx = (currentPoint.x - previousPoint!.x) * 0.5
                      my = (currentPoint.y - previousPoint!.y) * 0.5
                  }
      
                  let ctrlPt2 = CGPoint(x: currentPoint.x - mx / 3.0, y: currentPoint.y - my / 3.0)
      
                  addCurve(to: endPt, controlPoint1: ctrlPt1, controlPoint2: ctrlPt2)
              }
      
              if closed { close() }
          }
      
          /// Create smooth UIBezierPath using Catmull-Rom Splines
          ///
          /// This requires at least four points.
          ///
          /// Adapted from https://github.com/jnfisher/ios-curve-interpolation
          /// See http://spin.atomicobject.com/2014/05/28/ios-interpolating-points/
          ///
          /// - parameter catmullRomInterpolatedPoints: The array of CGPoint values.
          /// - parameter closed:                       Whether the path should be closed or not
          /// - parameter alpha:                        The alpha factor to be applied to Catmull-Rom spline.
          ///
          /// - returns:  An initialized `UIBezierPath`, or `nil` if an object could not be created for some reason (e.g. not enough points).
      
          convenience init?(catmullRomInterpolatedPoints points: [CGPoint], closed: Bool, alpha: Float) {
              self.init()
      
              guard points.count > 3 else { return nil }
      
              assert(alpha >= 0 && alpha <= 1.0, "Alpha must be between 0 and 1")
      
              let endIndex = closed ? points.count : points.count - 2
      
              let startIndex = closed ? 0 : 1
      
              let kEPSILON: Float = 1.0e-5
      
              move(to: points[startIndex])
      
              for index in startIndex ..< endIndex {
                  let nextIndex = (index + 1) % points.count
                  let nextNextIndex = (nextIndex + 1) % points.count
                  let previousIndex = index < 1 ? points.count - 1 : index - 1
      
                  let point0 = points[previousIndex]
                  let point1 = points[index]
                  let point2 = points[nextIndex]
                  let point3 = points[nextNextIndex]
      
                  let d1 = hypot(Float(point1.x - point0.x), Float(point1.y - point0.y))
                  let d2 = hypot(Float(point2.x - point1.x), Float(point2.y - point1.y))
                  let d3 = hypot(Float(point3.x - point2.x), Float(point3.y - point2.y))
      
                  let d1a2 = powf(d1, alpha * 2)
                  let d1a  = powf(d1, alpha)
                  let d2a2 = powf(d2, alpha * 2)
                  let d2a  = powf(d2, alpha)
                  let d3a2 = powf(d3, alpha * 2)
                  let d3a  = powf(d3, alpha)
      
                  var controlPoint1: CGPoint, controlPoint2: CGPoint
      
                  if fabs(d1) < kEPSILON {
                      controlPoint1 = point2
                  } else {
                      controlPoint1 = (point2 * d1a2 - point0 * d2a2 + point1 * (2 * d1a2 + 3 * d1a * d2a + d2a2)) / (3 * d1a * (d1a + d2a))
                  }
      
                  if fabs(d3) < kEPSILON {
                      controlPoint2 = point2
                  } else {
                      controlPoint2 = (point1 * d3a2 - point3 * d2a2 + point2 * (2 * d3a2 + 3 * d3a * d2a + d2a2)) / (3 * d3a * (d3a + d2a))
                  }
      
                  addCurve(to: point2, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
              }
      
              if closed { close() }
          }
      
      }
      
      // Some functions to make the Catmull-Rom splice code a little more readable.
      // These multiply/divide a `CGPoint` by a scalar and add/subtract one `CGPoint`
      // from another.
      
      private func * (lhs: CGPoint, rhs: Float) -> CGPoint {
          return CGPoint(x: lhs.x * CGFloat(rhs), y: lhs.y * CGFloat(rhs))
      }
      
      private func / (lhs: CGPoint, rhs: Float) -> CGPoint {
          return CGPoint(x: lhs.x / CGFloat(rhs), y: lhs.y / CGFloat(rhs))
      }
      
      private func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
          return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
      }
      
      private func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
          return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
      }
      

      Here are the "simple" smoothing algorithm, "Hermite" spline, and "Catmull Rom" spline curves in red, blue, and green, respectively. As you can see, the "simple" smoothing algorithm is computationally more simple, but generally doesn't pass through many of the points (but offers a more dramatic smoothing that eliminates any unsteadiness in the stroke). The points jumping around like this are exaggerating the behavior, whereas in a standard "gesture", it offers a pretty decent smoothing effect. The splines, on the other hand smooth the curve while passing through the points in the array.

    2. If targeting iOS 9 and later, it introduces some nice features, notably:

      • Coalesced touches in case the user is using a device capable of such, notably the newer iPads. Bottom line, these devices (but not the simulators for them) are capable of generating more than 60 touches per second, and thus you can get multiple touches reported for each call to touchesMoved.

      • Predicted touches, where the device can show you where it anticipates the user's touches will progress (resulting in less latency in your drawing).

      Pulling those together, you might do something like:

      var points: [CGPoint]?
      var path: UIBezierPath?
      
      override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
          if let touch = touches.first {
              points = [touch.location(in: view)]
          }
      }
      
      override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
          if let touch = touches.first {
              if #available(iOS 9.0, *) {
                  if let coalescedTouches = event?.coalescedTouches(for: touch) {
                      points? += coalescedTouches.map { $0.location(in: view) }
                  } else {
                      points?.append(touch.location(in: view))
                  }
      
                  if let predictedTouches = event?.predictedTouches(for: touch) {
                      let predictedPoints = predictedTouches.map { $0.location(in: view) }
                      pathLayer.path = UIBezierPath(catmullRomInterpolatedPoints: points! + predictedPoints, closed: false, alpha: 0.5)?.cgPath
                  } else {
                      pathLayer.path = UIBezierPath(catmullRomInterpolatedPoints: points!, closed: false, alpha: 0.5)?.cgPath
                  }
              } else {
                  points?.append(touch.location(in: view))
                  pathLayer.path = UIBezierPath(catmullRomInterpolatedPoints: points!, closed: false, alpha: 0.5)?.cgPath
              }
          }
      }
      
      override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
          path = UIBezierPath(catmullRomInterpolatedPoints: points!, closed: false, alpha: 0.5)
          pathLayer.path = path?.cgPath
      }
      

      In this code snippet, I'm rendering the path by updating a CAShapeLayer, but if you want to render it some other way, feel free. For example, using your drawRect approach, you'd update path, and then call setNeedsDisplay().

      And, the above illustrates the if #available(iOS 9, *) { ... } else { ... } syntax if you need to support iOS versions prior to 9.0, but obviously, if you are only supporting iOS 9 and later, you can remove that check and lose the else clause.

      For more information, see WWDC 2015 video Advanced Touch Input on iOS.

    Anyway, that yields something like:

    (For Swift 2.3 rendition of the above, please see the previous version of this answer.)

    这篇关于绘图类绘制直线而不是曲线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆