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()
}
}
controlPoint1
andcontrolPoint2
to produce a smooth-looking curve. Currently you are putting both control points halfway between the previous point and the next point, which produces a straight line. The tutorial that MrT linked to shows some ways to calculate reasonable control points. – Uturn