Removing old sections of curved line when drawing new sections in Swift - ios

I have Swift 2 code below that draws curved lines when the user touches and moves their finger across the screen. New sections of the curved line are added as the user moves their finger to give one continuous curved line on screen. (top image)
However, I wish to change the below code so that as each new section of the line is added and drawn to screen, the previous section and earlier sections are deleted, so that all is seen on the screen is the new section and nothing else. (bottom image)
What needs to be modified in the section of code below to achieve this?
// Swift 2 code below tested using Xcode 7.0.1.
class drawLines: UIView {
let path=UIBezierPath()
var previousPoint:CGPoint = CGPoint.zero
var strokeColor:UIColor?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect) {
strokeColor = UIColor.blackColor()
strokeColor?.setStroke()
path.lineWidth = 10
path.stroke()
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch: AnyObject? = touches.first
let currentPoint = touch!.locationInView(self)
path.moveToPoint(currentPoint)
previousPoint=currentPoint
self.setNeedsDisplay()
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch: AnyObject? = touches.first
let currentPoint = touch!.locationInView(self)
let midPoint = self.midPoint(previousPoint, p1: currentPoint)
path.addQuadCurveToPoint(midPoint,controlPoint: previousPoint)
previousPoint=currentPoint
self.setNeedsDisplay()
path.moveToPoint(midPoint)
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.setNeedsDisplay()
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
self.touchesEnded(touches!, withEvent: event)
}
func midPoint(p0:CGPoint,p1:CGPoint)->CGPoint
{
let x=(p0.x+p1.x)/2
let y=(p0.y+p1.y)/2
return CGPoint(x: x, y: y)
}
}

As far as I know, there's no way to remove points from a UIBezierPath. The solution you're looking for is to store the points in an array, which you can modify freely, then create your path from that point array.
Update
I'm getting a bit confused re-reading your post alongside the comments you've posted. That being said, I've tried to make the example below contain all the elements you need – you might have to re-arrange them if I have not understood you 100%.
This code continuously adds points to an array from the point a touch begins, including while it moves. I added a cap to the maximum number of points so that it removes excess line points if needed. When the user releases their finger I've made it clear all the points.
Note: I've tried to keep the code as simple as possible so that it's clear. Once you find the correct combination of adding/removing points that matches your needs, you should probably look at optimising this. In particular, this assumes a naïve method of drawing where all drawing happens simultaneously (vs deltas), which isn't efficient. The method of removing excess points can also be optimised once you understand it.
import UIKit
class ViewController: UIViewController {
var points = [CGPoint]()
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
addTouch(touches.first)
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
addTouch(touches.first)
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
points.removeAll()
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
points.removeAll()
}
func addTouch(touch: UITouch?) {
guard let touch = touch else { return }
let currentPoint = touch.locationInView(self.view)
points.append(currentPoint)
// if you want a limit on your line length, you need these lines
// 20 is an arbitrary number
while (points.count > 20) {
points.removeFirst()
}
}
}
All that remains is for you to add that to your current line drawing code, and you should be good to go.
Apple have done a number of good WWDC talks on graphics performance. This one in particular might be helpful.

If I understand your question correctly, you'll want to reset the
path in the touchesBegan() method
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
path.removeAllPoints()
// ...
}

You could save an array of points and rebuild your bezier path each time as TwoStraws recommends.
Another option would be to draw your curve using a CAShapeLayer, and change the shapeBegin property. (As you move from shapeBegin = 1.0 to shapeBegin = 0.0 it truncates the beginning of the shape.)
A third option would be to edit the path to delete the earlier points. Erica Sadun has sample code in her outstanding iOS Developers' Cookbook series that shows how to parse the points inside a Bezier path (If I remember correctly it actually uses the underlying CGPath object that is inside a Bezier path.) That would be much faster than rebuilding your bezier path each time, but more work for you.

Related

Button changing after image moves over it?

I am making a "slide to confirm" button, the set up is a custom UIButton + a regular UIImageView, the image is passed to the button and the button listen to touches and move the image accordingly.
Everything is working great except for one detail after the button is released everything should go back to their initial state, yet the button stay changed as if its alpha was halved
i commented the alpha because the button was going to hide but i disabled that once i run into this problem, the problem happens regardless of setting the alpha or not.
This is the code for the custom button
class SlideButton: UIButton {
var arrowImage: UIImageView?
var initialLocation: CGPoint?
var initialArrowPosition: CGPoint?
func setArrow(arrow:UIImageView){
arrowImage = arrow
initialArrowPosition = arrow.frame.origin
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
let touch = touches.first!
let location = touch.location(in: self.superview)
initialLocation = location
UIView.animate(withDuration: 0.2) {
//self.alpha = 0.0
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
UIView.animate(withDuration: 0.3) {
self.arrowImage!.frame.origin = self.initialArrowPosition!
//self.alpha = 1.0
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
let currentLocation = touch.location(in: self.superview)
arrowImage!.frame.origin.x = initialArrowPosition!.x - initialLocation!.x + (currentLocation.x)
}
}
You are remembering to call super for one of your touches methods:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
But for the other touches methods you've forgotten to do that, so you've broken the core functionality of the button.
you might have to also add an override to touchesCancelled because you might not always hit the touchesEnded endpoint.

Drawing performance with Swift in Xcode for iOS app

I have a function for drawing lines, edited below. It seems to work fine in the simulator, however there are performance issues, lines drawn slowly, when running on both an older iPhone (2011) and a newer iPad (2014). I believe this issue is due to creating a new CGContext for every touchesMoved event received.
How can I, for example, call let context = UIGraphicsGetCurrentContext() once when touchesBegan? (i.e. how can I make context a public variable that can be called once?)
Any other tips for improving the performance would be appreciated. Thank you.
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
autoreleasepool {
UIGraphicsBeginImageContextWithOptions(view.frame.size, false, 0.0)
let context = UIGraphicsGetCurrentContext()
...
...
...
UIGraphicsEndImageContext()
}
}
Do not execute drawing code in touchesMoved. You should store whatever you need to update your drawing (probably touch location), then call setNeedsDisplay. That will force a call to drawRect: which would contain all of your drawing code. You do not need to create a context, just use UIGraphicsGetCurrentContext().
Here is a contrived UIView subclass example that draws a red circle underneath the latest touch point.
class DrawView: UIView {
let circleSize:CGFloat = 50.0
var lastTouchPoint:CGPoint?
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
lastTouchPoint = touches.first?.locationInView(self)
self.setNeedsDisplay()
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
lastTouchPoint = touches.first?.locationInView(self)
self.setNeedsDisplay()
}
override func drawRect(rect: CGRect) {
if let touchPoint = lastTouchPoint {
let context = UIGraphicsGetCurrentContext()
CGContextSetRGBFillColor (context, 1, 0, 0, 1);
CGContextFillEllipseInRect(context, CGRectMake(touchPoint.x - circleSize/2.0, touchPoint.y - circleSize/2.0, circleSize , circleSize))
}
}
}

locationInNode returning erroneous results

I have a subclass of SKNode called Player which pretty much consists of this: http://hub.ae/blog/2014/03/26/soft-body-physics-jellyusing-spritekit/ converted to swift (With a few changes). I've allowed the user to move the player node with his finger with the following code:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
player.runAction(SKAction.moveTo(touch.locationInNode(world), duration: 1))
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
player.runAction(SKAction.moveTo(touch.locationInNode(world), duration: 1))
}
override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
player.removeAllActions()
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
player.removeAllActions()
}
*As a note, the 'world' var you see is just another SKNode. 'player' is a child of this node.
This looks like it should work, however the node moves in very strange directions. This is an example of how it looks:
http://gyazo.com/87b0d09101bbbfd3ac0f2a3cdbf42e4c
How can I fix this? I found that settings the anchor point of the scene to 0.5 fixes this issue, however then the physics body of 'player' node gets messed up.
I had same problem but i can not remember if it is how i fixed it. Try changing this:
world > self
player.runAction(SKAction.moveTo(touch.locationInNode(world), duration: 1))
To:
player.runAction(SKAction.moveTo(touch.locationInNode(self), duration: 1))
If i am wrong let me know :)
EDIT
set anchor point to (0.5,0.5) and write those 2 methods to center on your node.
override func didSimulatePhysics() {
camera.position = CGPointMake(player.position.x, player.position.y);
centerOnNode(camera)
}
func centerOnNode(node: SKNode){
var positionInScene : CGPoint = convertPoint(node.position, fromNode: node.parent)
world.position = CGPointMake(world.position.x - positionInScene.x, world.position.y - positionInScene.y)
}

Moving objects according to touchesMoved

So I'm trying to move an object using the touches moved function, however I'm not sure how to do that when the coordinates are not predetermined. This is what I've got going :
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
var touch: AnyObject? = touches.anyObject()
var point = touch?.locationInView(self.view)
}
So basically I'm making the point variable the coordinates (points).
Then what needs to happen is that i move an object (in this case Label1) to that location:
func MoveObject(point) {
Label1.frame.origin = ( /* Here would the ´point´ be */ )
}
Also keep in mind that later when this problem is out of the way I'm going to change the location so its not where you touch but it moves accordingly. I don't know if that matters really but I'm just saying in case it does ;)
What about
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
var touch: AnyObject? = touches.anyObject()
var point = touch?.locationInView(self.view)
moveObject(point)
}
...
func MoveObject(point) {
Label1.center = point
}

Performance issues pertaining to drawRect in iOS on Swift

Thank you for taking the time to read my thread. Recently i’ve been developing a simple iOS free-draw app and have run in to an issue that’s currently above my skill level to solve. I’ve been scouring the internet for days to try and come up with a solution but have had no luck thus far. Fortunately I have thought of a remedy for my applications lag issue, however I still need help as really do not know how to implement it.
A brief description of how this part of the program operates:
As the user moves his/her finger across the screen ( in a UIView labelled: view_Draw), touchesBegan() and touchesMoved() interpret start and end points for x&y coordinates and store these coordinates in an array (lines). The drawView is then forced to update via setNeedsDisplay().
class view_Draw: UIView {
// view_Draw Class Variables
var lastPoint: CGPoint!
var drawColor:UIColor = UIColor.redColor()
required init(coder aDecoder:NSCoder) {
super.init(coder: aDecoder)
self.backgroundColor = UIColor.whiteColor()
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
lastPoint = touches.anyObject()?.locationInView(self)
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
var newPoint = touches.anyObject()?.locationInView(self)
lines.append(Line(start: self.lastPoint, end: newPoint!, color: self.drawColor))
self.lastPoint = newPoint
setNeedsDisplay()
}
override func drawRect(rect: CGRect) {
var context = UIGraphicsGetCurrentContext()
//set stroke style
CGContextSetLineCap(context, kCGLineCapRound)
//set brush parameters
CGContextSetStrokeColorWithColor(context, drawColor.CGColor)
CGContextSetAlpha(context, 0.95)
CGContextSetLineWidth(context, brushSize)
for line in lines {
CGContextBeginPath(context)
CGContextMoveToPoint(context, line.start.x, line.start.y)
CGContextAddLineToPoint(context, line.end.x, line.end.y)
CGContextStrokePath(context)
// CGBitmapContextCreateImage(context)
// CGBitmapContextReleaseDataCallback()
}
}
}
A brief description of the issue:
As the user continues draws on the screen, I notice in the instruments panel that the CPU on thread 1 reaches to around 95% – 100%. This causes elements in my program (timers, drawing response) to begin lagging.
Actions taken to remedy issue:
I’ve experimented by disabling setNeedsDisplay() and have discovered that filling the lines array equates to only 10% of the overall CPU demand. From what I understand, this is because nothing from drawRect is being applied to the coordinates within the lines array.
Disabling CGContextStrokePath() and enabling setNeedsDisplay() increases CPU demand to 49%. I've interpreted this as the coordinates within the lines array are now being manipulated by drawRect- however are not actually being drawn onto the view.
This means that by forcing setNeedsDisplay() to update with CGContextStrokePath enabled, it’s hogging roughly 85% – 90% of the available processing power of thread 1.
I’ve also experimented with adding a timer to control how often setNeedsDisplay forces an update, but the results are less than acceptable. Drawing feels choppy with this in place.
Proposed remedy:
I think that the principle issue is that setNeedsDisplay() is redrawing the entirety of the lines array- what the user has drawn, constantly while touchesMoved() is being accessed.
I have looked into potentially using GCD to try and take some load off of thread 1, however after reading up on it, it seems as though this would not be a 'safe' way. From what i've understood, GCD and/or dispatch_async... shouldn't be used for elements that directly interact with UI elements.
I’ve seen on various forums that people have tackled similar issues by converting the existing path context to a bitmap and only updating the newly generated path with setNeedsDisplay.
I’m hoping that by approaching the issue this way, the setNeedsDisplay will not have to draw the entire array live every time as the previously drawn lines will have been converted into a static image. I have run out of ideas on how to even start implementing this.
As you can probably tell, I started learning Swift only a few weeks ago. I am doing my best to learn and approach problems in an effective manner. If you have any suggestions on how I should proceed with this, it would be greatly appreciated. Again, thank you for your help.
Answer based upon Aky's Smooth Freehand Drawing on iOS tutorial
By implementing the following code, a "buffer" of sorts is created that helps lessen the load on thread 1. In my initial tests, the load topped out at 46% while drawing as opposed to my original programs load that would top out at 95%-100%
LinearInterpView.swift
import UIKit
class LinearInterpView:UIView {
var path = UIBezierPath() //(3)
var bezierPath = UIBezierPath()
required init(coder aDecoder:NSCoder) { //(1)
super.init(coder: aDecoder)
self.multipleTouchEnabled = false //(2)
self.backgroundColor = UIColor.whiteColor()
path = bezierPath
path.lineWidth = 40.0
}
override func drawRect(rect: CGRect) { //(5)
UIColor.blackColor().setStroke()
path.stroke()
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
var touch:UITouch = touches.anyObject() as UITouch
var p:CGPoint = touch.locationInView(self)
path.moveToPoint(p)
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
var touch:UITouch = touches.anyObject() as UITouch
var p:CGPoint = touch.locationInView(self)
path.addLineToPoint(p) //(4)
setNeedsDisplay()
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
touchesMoved(touches, withEvent: event)
}
override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {
touchesEnded(touches, withEvent: event)
}
}
CachedLIView.swift
import UIKit
class CachedLIView:UIView {
var path = UIBezierPath()
var bezierPath = UIBezierPath() // needed to add this
var incrementalImage = UIImage() //(1)
var firstRun:Bool = true
required init(coder aDecoder:NSCoder) {
super.init(coder: aDecoder)
self.multipleTouchEnabled = false
self.backgroundColor = UIColor.whiteColor()
path = bezierPath
path.lineWidth = 40.0
}
override func drawRect(rect: CGRect) {
incrementalImage.drawInRect(rect) //(3)
path.stroke()
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
var touch:UITouch = touches.anyObject() as UITouch
var p:CGPoint = touch.locationInView(self)
path.moveToPoint(p)
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
var touch:UITouch = touches.anyObject() as UITouch
var p:CGPoint = touch.locationInView(self)
path.addLineToPoint(p)
self.setNeedsDisplay()
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) { //(2)
var touch:UITouch = touches.anyObject() as UITouch
var p:CGPoint = touch.locationInView(self)
path.addLineToPoint(p)
self.drawBitmap() //(3)
self.setNeedsDisplay()
path.removeAllPoints() //(4)
}
override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {
touchesEnded(touches, withEvent: event)
}
func drawBitmap() { //(3)
var rectPath = UIBezierPath()
UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 0.0)
UIColor.blackColor().setStroke()
if(firstRun == true) {
rectPath = UIBezierPath(rect: self.bounds)
UIColor.whiteColor().setFill()
rectPath.fill()
firstRun = false
}
incrementalImage.drawAtPoint(CGPointZero)
path.stroke()
incrementalImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
Thank you again for your help, I hope this can be of use to others as well.
I wrote a couple of tutorials a few years ago about drawing in iOS using Objective-C. You might be able to translate this code into Swift without too many problems.
The first article talks about how you can render the on-screen graphics context into an image every time the user lifts his finger from the screen, so you can generate a new path from fresh from that point onwards and just draw it on top of that image.
Smooth Freehand Drawing on iOS
The second one has a GCD-based implementation that lets you do this rendering in the background.
Advanced Freehand Drawing Techniques

Resources