Swift Recursive Back Tracking Algorithm Not Working - algorithm

Hi I have written a class in swift that should create a maze through a recursive backtracking algorithm. I seem to have a problem around the assignment of walls to my maze. But I can not crack it.
It would be great if I could get some help here, thank you!
Please find a description of the code below
2D Array Class - Pretty self explanatory. Give it a number of columns and rows and a default value, from there it generate the 2d array. The subscript methods allow you to set and get values.
class Array2D<T> {
let columns: Int
let rows: Int
var array : Array<Array<T?>>
init(columns: Int, rows: Int, repeatedValue: T?) {
self.columns = columns
self.rows = rows
var tmp = Array<T?>(count: rows, repeatedValue: repeatedValue)
array = Array<Array<T?>>(count: columns, repeatedValue: tmp)
}
subscript(column: Int, row: Int) -> T? {
get {
return array[column][row]
}
set(newValue) {
array[column][row] = newValue
}
}
}
DIR enum A enum which allows us to abstract ourselves from bits by assigning them names.
enum DIR : UInt8 {
case N = 1
case S = 2
case E = 4
case W = 8
case O = 0
}
Direction Class This class holds all the information about the Directions. It may be slightly an overkill. It allows you to get directions (N,S,E,W) in relation to a current location. You can also get the opposite direction.
class Direction {
var bit : DIR
var dx : Int
var dy : Int
init(bit: DIR, dx: Int, dy: Int) {
self.bit = bit
self.dx = dx
self.dy = dy
}
class func NORTH() -> Direction {
return Direction(bit: DIR.N, dx: 0, dy: -1)
}
class func SOUTH() -> Direction {
return Direction(bit: DIR.S, dx: 0, dy: 1)
}
class func EAST() -> Direction {
return Direction(bit: DIR.E, dx: 1, dy: 0)
}
class func WEST() -> Direction {
return Direction(bit: DIR.W, dx: -1, dy: 0)
}
func opposite() -> Direction {
switch(bit){
case DIR.N:
return Direction.SOUTH()
case DIR.S:
return Direction.NORTH()
case DIR.E:
return Direction.WEST()
case DIR.W:
return Direction.EAST()
default:
println("An error occured while returning the opposite of the direction with bit: \(bit)")
return Direction(bit: DIR.O, dx: 0, dy: 0)
}
}
}
RecursiveBackTracking Class This is where the magic happens. This class auto generates a maze given a width(x) and a height(y). The generateMaze() function does most of the work with the other functions in support. Everything here seems to work but I am still not getting the appropriate result. Potentially the problem may also by in the display() function.
class RecursiveBacktracking {
var x : Int
var y : Int
var maze : Array2D<UInt8>
init(x: Int, y: Int) {
self.x = x
self.y = y
maze = Array2D<UInt8>(columns: x, rows: y, repeatedValue: 0)
generateMaze(0, cy: 0)
display()
}
func generateMaze(cx: Int, cy: Int) {
var directions : [Direction] = [Direction.NORTH(),Direction.SOUTH(),Direction.EAST(),Direction.WEST()]
directions = shuffle(directions)
for dir in directions {
var nx : Int = cx + dir.dx
var ny : Int = cx + dir.dy
if between(nx, upper: x) && between(ny, upper: y) && getMazeObject(nx, y: ny) == 0 {
maze[cx,cy] = bitwiseOr(getMazeObject(cx, y: cy), b: dir.bit.rawValue)
maze[nx,ny] = bitwiseOr(getMazeObject(nx, y: ny), b: dir.opposite().bit.rawValue)
generateMaze(nx, cy: ny)
}
}
}
func bitwiseOr(a: UInt8, b: UInt8) -> UInt8 {
return a | b
}
func getMazeObject(x: Int, y: Int) -> UInt8 {
if var object = maze[x,y] {
return object
}else{
println("No object could be found at location: (\(x),\(y)).")
return 0
}
}
func between(v: Int, upper: Int) -> Bool {
return (v>=0) && (v<upper)
}
func shuffle<C: MutableCollectionType where C.Index == Int>(var list: C) -> C {
let count : Int = Int(countElements(list))
for i in 0..<(count - 1) {
let j = Int(arc4random_uniform(UInt32(count - i))) + i
swap(&list[i], &list[j])
}
return list
}
func display() {
for i in 0..<y {
// Draw North Edge
for j in 0..<x {
var bit : UInt8 = getMazeObject(j, y: i)
bit = bit & 1
if bit == 0 {
print("+---")
}else{
print("+ ")
}
}
println("+")
// Draw West Edge
for j in 0..<x {
var bit : UInt8 = getMazeObject(j, y: i)
bit = bit & 8
if bit == 0 {
print("| ")
}else{
print(" ")
}
}
println("|")
}
// Draw the bottom line
for j in 0..<x {
print("+---")
}
println("+")
}
}
Additional Information: This algorithm is based off of http://rosettacode.org/wiki/Maze#Java

The error is here:
var nx : Int = cx + dir.dx
var ny : Int = cx + dir.dy
The second cx should be cy.
Remark: There is some room for improvement in your code. As an example, the
bitwise or | is already defined for UInt8, so there is no need to define that
as a function. If you have fixed your code to work correctly, you might consider
to post it at http://codereview.stackexchange.com to get a review.

Related

Invalid Array Width without declaring new dimension

My web app is generating an "Invalid Array Width" error at line 462 of Crossfilter.js v1.3.12. This error seems to tell me I have >32 dimensions. The puzzle is that I am not knowingly declaring a new dimension when the error occurs.
I have 10 slider bars, which act as numeric filters on my dataset. At the end of a drag event on the second slider bar, a dimension is declared if none already exists at the second location within the numericDims array. (Edit: even when I declare all the 10 dimensions in advance, and remove the dynamic declaration, the problem still occurs.) About 10 dimensions already exist in the app for other graphics & filters.
The first time I move a slider handle, "new dimension" is logged. After that, every time I move a handle on the same slider, "new dimension" is not logged. This is expected behaviour. But if I move the handles enough times, I get the "Invalid Array Width" error. So, I think I must be accidentally declaring a new dimension every time I move a handle. Can anyone see how I am unwittingly declaring a new dimension? The most relevant code:
if (!numericDims[tempIndex]) {
console.log('new dimension');
numericDims[tempIndex] = facts.dimension(function(p){ return p[d]; });
}
if (flag==0) {
prt.classed("activeFilter",true);
numericDims[tempIndex].filterFunction(function(p){ return p>=min && p<=max; });
} else {
prt.classed("activeFilter",false);
numericDims[tempIndex].filterAll();
// numericDims[tempIndex].dispose(); ***I figure it's quicker to store them instead of disposing/deleting. Even when I dispose/delete, the problem still happens.
// delete numericDims[tempIndex];
// numericDims.splice(tempIndex,1);
prt.selectAll("g.handle.left").attr("title",null);
prt.selectAll("g.handle.right").attr("title",null);
}
console.log(numericDims);
Full function:
function dragended(d) {
let transformation = {
Y: Math.pow(10, 24),
Z: Math.pow(10, 21),
E: Math.pow(10, 18),
P: Math.pow(10, 15),
T: Math.pow(10, 12),
G: Math.pow(10, 9),
M: Math.pow(10, 6),
k: Math.pow(10, 3),
h: Math.pow(10, 2),
da: Math.pow(10, 1),
d: Math.pow(10, -1),
c: Math.pow(10, -2),
m: Math.pow(10, -3),
μ: Math.pow(10, -6),
n: Math.pow(10, -9),
p: Math.pow(10, -12),
f: Math.pow(10, -15),
a: Math.pow(10, -18),
z: Math.pow(10, -21),
y: Math.pow(10, -24)
}
let reverse = s => {
let returnValue;
Object.keys(transformation).some(k => {
if (s.indexOf(k) > 0) {
returnValue = parseFloat(s.split(k)[0]) * transformation[k];
return true;
}
})
return returnValue;
}
var facts = window.facts;
if (d3.select(this).attr("class").indexOf("left")==-1) { var otherHandle = 'left'; } else { var otherHandle = 'right'; }
d3.select(this).classed("dragging",false);
var filterFields = window.filterFields;
var tempIndex = filterFields[0].indexOf(d);
var min = filterFields[2][tempIndex];
var max = filterFields[3][tempIndex];
//console.log(min+', '+max);
var scale = filterFields[4][tempIndex];
var t = d3.transform(d3.select(this).attr("transform"));
var thisX = t.translate[0];
var flag=0;
var prt = d3.select("g#f_"+tempIndex);
var leftHandleX = d3.transform(prt.selectAll("g.handle.left").attr("transform")).translate[0];
var rightHandleX = d3.transform(prt.selectAll("g.handle.right").attr("transform")).translate[0];
var wid = prt.selectAll("g.axis").select("rect.numFilterBox").attr("width");
prt.selectAll("g.axis").select("rect.numFilterBox").attr("x",leftHandleX).attr("width",rightHandleX - leftHandleX);
var num = -1;
var pFlag = 0;
if (filterFields[3][tempIndex]<=1) { var fmt = d3.format('%'); pFlag=1; } else { var fmt = d3.format('4.3s'); }
if (otherHandle=='left') {
if (thisX>=300 && scale(min)==0) { flag=1; }
max = scale.invert(thisX);
if (isNaN(+fmt(max).trim())) {
if (pFlag==1) {
max = +fmt(max).substr(0,fmt(max).length-1)/100
} else {
max = reverse(fmt(max));
}
} else {
max = +fmt(max).trim();
}
prt.selectAll("g.handle.right").attr("title",function(d){ return 'The filtered maximum for '+filterFields[1][tempIndex]+' is '+max; });
} else {
if (thisX<=0 && scale(max)==300) { flag=1; }
min = scale.invert(thisX);
if (isNaN(+fmt(min).trim())) {
if (pFlag==1) {
min = +fmt(min).substr(0,fmt(min).length-1)/100
} else {
min = reverse(fmt(min));
}
} else {
min = +fmt(min).trim();
}
prt.selectAll("g.handle.left").attr("title",function(d){ return 'The filtered minimum for '+filterFields[1][tempIndex]+' is '+min; });
}
filterFields[2][tempIndex] = min;
filterFields[3][tempIndex] = max;
window.filterFields = filterFields;
if (!numericDims[tempIndex]) {
console.log('new dimension');
numericDims[tempIndex] = facts.dimension(function(p){ return p[d]; });
}
if (flag==0) {
prt.classed("activeFilter",true);
numericDims[tempIndex].filterFunction(function(p){ return p>=min && p<=max; });
} else {
prt.classed("activeFilter",false);
numericDims[tempIndex].filterAll();
// numericDims[tempIndex].dispose();
// delete numericDims[tempIndex];
// numericDims.splice(tempIndex,1);
prt.selectAll("g.handle.left").attr("title",null);
prt.selectAll("g.handle.right").attr("title",null);
}
console.log(numericDims);
update();
doHighlight();
window.dragFlag=1;
}

Sorting Strings by Character and Length

In my Android app, I am trying to sort Bus route tags in order 1, 2, 3..etc.
For that I am using this
Collections.sort(directions, Comparator { lhs, rhs ->
var obj1 = lhs.short_names.firstOrNull() ?: ""
var obj2 = rhs.short_names.firstOrNull() ?: ""
if (obj1 === obj2) {
obj1 = lhs.headsigns.firstOrNull() ?: ""
obj2 = rhs.headsigns.firstOrNull() ?: ""
if (obj1 === obj2) {
return#Comparator 0
}
obj1.compareTo(obj2)
} else {
obj1.compareTo(obj2)
}
The issue I am having is this sorts them, but will run into the issue of
1, 2, 3, 30, 31, 4, 5
How should I change this to get the correct ordering.
If you need just a simple number comparison you can do it like that.
directions.sortWith(Comparator { lhs, rhs ->
val i1 = lhs.toInt()
val i2 = rhs.toInt()
when {
i1 < i2 -> -1
i1 > i2 -> 1
else -> 0
}
})
As hotkey pointed out the code above can be replaced with almost identical implementation that looks much simplier.
directions.sortBy { it.toInt() }
The general version of this algorithm is called alphanum sorting and described in details here. I made a Kotlin port of this algorithm, which you can use. It's more complicated than what you need, but it will solve your problem.
class AlphanumComparator : Comparator<String> {
override fun compare(s1: String, s2: String): Int {
var thisMarker = 0
var thatMarker = 0
val s1Length = s1.length
val s2Length = s2.length
while (thisMarker < s1Length && thatMarker < s2Length) {
val thisChunk = getChunk(s1, s1Length, thisMarker)
thisMarker += thisChunk.length
val thatChunk = getChunk(s2, s2Length, thatMarker)
thatMarker += thatChunk.length
// If both chunks contain numeric characters, sort them numerically.
var result: Int
if (isDigit(thisChunk[0]) && isDigit(thatChunk[0])) {
// Simple chunk comparison by length.
val thisChunkLength = thisChunk.length
result = thisChunkLength - thatChunk.length
// If equal, the first different number counts.
if (result == 0) {
for (i in 0..thisChunkLength - 1) {
result = thisChunk[i] - thatChunk[i]
if (result != 0) {
return result
}
}
}
} else {
result = thisChunk.compareTo(thatChunk)
}
if (result != 0) {
return result
}
}
return s1Length - s2Length
}
private fun getChunk(string: String, length: Int, marker: Int): String {
var current = marker
val chunk = StringBuilder()
var c = string[current]
chunk.append(c)
current++
if (isDigit(c)) {
while (current < length) {
c = string[current]
if (!isDigit(c)) {
break
}
chunk.append(c)
current++
}
} else {
while (current < length) {
c = string[current]
if (isDigit(c)) {
break
}
chunk.append(c)
current++
}
}
return chunk.toString()
}
private fun isDigit(ch: Char): Boolean {
return '0' <= ch && ch <= '9'
}
}
To use this Comparator just call
directions.sortWith(AlphanumComparator())
If you don't need it to be coded in Kotlin you can just take an original Java version on Dave Koelle's page. And the Kotlin version of the algorithm can be also found on GitHub.

Plotting multiple scatter lines in different rate?

I used CorePlot to implement real-time plotting but I have two different data in different sampling rate.
My ECG sampling rate is 250Hz and Resp sampling rate is 50Hz. I want to do the real-time plotting in the same graph.
At first, I tried to modify the array in different count, but when it goes to numberForPlot function, the error happened. It seems like the recordIndex should be the same count.
How should I do? Should I modify the CorePlot method? Or just modify my code?
Thank you!
func plotChartTest(ecg:[Int], resp:[Int], packNum: Int){
if (packNum == 1){
...
self.newGraph.addPlot(respLinePlot)
self.newGraph.addPlot(ecgLinePlot)
// Add some initial data
for i in 0 ..< tempEcgArray.count {
let x = Double(i) * 0.01
let y = 0.0
//let y = Double(fecg[i])/65535.0 + 0.05
let dataPoint: plotDataType = [.X: x, .Y:y]
self.ecgDataForPlot.append(dataPoint)
//ecgContentArray.append(dataPoint)
}
//self.ecgDataForPlot = ecgContentArray
for i in 0 ..< tempRespArray.count {
let x = Double(i) * 0.01
let y = 0.0
let dataPoint: plotDataType = [.X: x, .Y: y]
self.respDataForPlot.append(dataPoint)
}
// self.respDataForPlot = respContentArray
}
if (currentIndex % 50 == 0){
let i = 0
respDataForPlot.removeAtIndex(currentIndex+i)
respDataForPlot.insert([.X: Double(currentIndex+i)*0.01, .Y: Double(resp[i])/65535.0], atIndex: currentIndex+i)
respLinePlot.deleteDataInIndexRange(NSMakeRange(currentIndex+i, 1))
respLinePlot.insertDataAtIndex(UInt(currentIndex+i), numberOfRecords: 1)
}
for i in 0 ..< 5{
ecgDataForPlot.removeAtIndex(currentIndex+i)
ecgDataForPlot.insert([.X: Double(currentIndex+i)*0.01, .Y: Double(ecg[i])/65535.0], atIndex: currentIndex+i)
ecgLinePlot.deleteDataInIndexRange(NSMakeRange(currentIndex+i, 1))
ecgLinePlot.insertDataAtIndex(UInt(currentIndex+i), numberOfRecords: 1)
}
currentIndex = currentIndex + 5
}
// MARK: - Plot Data Source Methods
//Set the number of data points the plot has
func numberOfRecordsForPlot(plot: CPTPlot) -> UInt
{
let plotID = plot.identifier as! String
if (plotID == "ECG"){
return UInt(self.ecgDataForPlot.count)
}
else if (plotID == "RESP"){
return UInt(self.respDataForPlot.count)
}
return 0
}
//Returns the data point for each plot by using the plot identifier set above
func numberForPlot(plot: CPTPlot, field: UInt, recordIndex: UInt) -> AnyObject?
{
let plotField = CPTScatterPlotField(rawValue: Int(field))
let plotID = plot.identifier as! String
print("ID: \(plotID), Index: \(recordIndex)")
if let respNum = self.respDataForPlot[Int(recordIndex)][plotField!], let ecgNum = self.ecgDataForPlot[Int(recordIndex)][plotField!]{
if (plotField! == .Y) && (plotID == "RESP") {
return respNum as NSNumber
}
else {
return ecgNum as NSNumber
}
}
else {
return nil
}
}
In the datasource numberForPlot() function, only unwrap the datapoint for a single plot. Since the data arrays have different lengths, the lookup will fail for the shorter array when plotting the longer one.
if (plotID == "ECG") {
if let ecgNum = self.ecgDataForPlot[Int(recordIndex)][plotField!] {
return ecgNum as NSNumber
}
else {
return 0 as NSNumber
}
}
else if (plotID == "RESP") {
if let respNum = self.respDataForPlot[Int(recordIndex)][plotField!] {
return respNum as NSNumber
}
else {
return 0 as NSNumber
}
}
else {
return 0 as NSNumber
}

Simulating human mouse movement

Hi I'm currently trying to create a program that moves the cursor from a given point to another in one smooth randomised motion. I currently have created the following using CoreGraphics, which works but the mouse movement gets very choppy. Any ideas on how to fix this? Much appreciated. I call the following at the start of my Mac OS X Application inside applicationDidFinishLaunching:
var pos = NSEvent.mouseLocation()
pos.y = NSScreen.mainScreen()!.frame.height - pos.y
moveMouse(CGPoint(x:200,y:200), from: pos)
And these are the functions I've created:
func transMouse(point:CGPoint) {
let move = CGEventCreateMouseEvent(nil, CGEventType.MouseMoved, point, CGMouseButton.Left)
CGEventPost(CGEventTapLocation.CGHIDEventTap, move)
}
func moveMouseOne(direction:Character, _ currentPos:CGPoint) -> CGPoint {
var newPos = currentPos
if direction == "r" {
newPos.x = currentPos.x + 1
} else if direction == "l" {
newPos.x = currentPos.x - 1
} else if direction == "u" {
newPos.y = currentPos.y - 1
} else if direction == "d" {
newPos.y = currentPos.y + 1
}
transMouse(newPos)
return newPos
}
func moveMouse(to:CGPoint, from:CGPoint) -> CGPoint {
let dx:Int = Int(to.x - from.x)
let dy:Int = Int(to.y - from.y)
var moves = Array<Character>()
if dx > 0 {
for _ in 0..<dx {
moves.append("r")
}
} else {
for _ in 0..<(-dx) {
moves.append("l")
}
}
if dy > 0 {
for _ in 0..<dy {
moves.append("d")
}
} else {
for _ in 0..<(-dy) {
moves.append("u")
}
}
var pos = from
let delay:Double = 0.0008
let startTime = DISPATCH_TIME_NOW
for var i = 0; i < moves.count; ++i {
let time = dispatch_time(startTime, Int64(delay * Double(i) * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue()) {
let count = moves.count
let random = Int(arc4random_uniform(UInt32(count)))
pos = self.moveMouseOne(moves[random], pos)
if random == count - 1 {
moves.popLast()
} else {
moves[random] = moves.popLast()!
}
}
}
return to
}
I really recommend using Core Animation for something like this, will save you a lot of time and effort.
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html

Drawing performance with multiple UIBezierPaths

I have a drawing app which is currently made up of a main View Controller which holds 4 separate UIViews which simultaneously replicate the line drawn on the touched quadrant across the other 3 with some axis reversed to make the drawing symmetrical.
When using this method the drawing is smooth and you can see that there are lots of points being collected when the user moves their finger as the line follows their movements quite well.
The code at a high level looks like this:
MainViewController.swift
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
var touch: UITouch = touches.anyObject() as UITouch
var p = CGPoint()
if touch.view == quadrant1 {
p = touch.locationInView(quadrant1)
quadrant1.began(p)
var p2 = CGPointMake(quadrant2.bounds.width - p.x, p.y)
quadrant2.began(p2)
var p3 = CGPointMake(p.x,quadrant3.bounds.height - p.y)
quadrant3.began(p3)
var p4 = CGPointMake(quadrant4.bounds.width - p.x, quadrant4.bounds.height - p.y)
quadrant4.began(p4)
} else if touch.view == quadrant2 {
...
Touches 'moved' and 'ended' call similar methods in each of the quadrants by doing the same calculations. The Quadrant files look like this:
Quadrant1,2,3,4.swift
// A counter to determine if there are enough points to make a quadcurve
var ctr = 0
// The path to stroke
var path = UIBezierPath()
// After the user lifts their finger and the line has been finished the same line is rendered to an image and the UIBezierPath is cleared to prevent performance degradation when lots of lines are on screen
var incrementalImage = UIImage()
// This array stores the points that make each line
var pts: [CGPoint] = []
override func drawRect(rect: CGRect) {
incrementalImage.drawInRect(rect)
path.stroke()
}
func began (beganPoint: CGPoint) {
ctr = 0
var p = beganPoint
pts.insert(beganPoint, atIndex: 0)
}
func moved(movedPoints: CGPoint) {
var p = movedPoints
ctr++
pts.insert(movedPoints, atIndex: ctr)
// This IF statement handles the quadcurve calculations
if ctr == 3 {
pts[2] = CGPointMake((pts[1].x + pts[3].x)/2.0, (pts[1].y + pts[3].y)/2.0);
path.moveToPoint(pts[0])
path.addQuadCurveToPoint(pts[2], controlPoint: pts[1])
self.setNeedsDisplay()
pts[0] = pts[2]
pts[1] = pts[3]
ctr = 1
}
}
func ended (endPoint: CGPoint) {
if ctr == 2 {
path.moveToPoint(pts[0])
path.addQuadCurveToPoint(pts[2], controlPoint: pts[1])
}
self.drawBitmap()
self.setNeedsDisplay()
path.removeAllPoints()
}
func drawBitmap() {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
var rectPath = UIBezierPath(rect: self.bounds)
UIColor.clearColor().setFill()
rectPath.fill()
incrementalImage.drawAtPoint(CGPointZero)
color.setStroke()
path.stroke()
incrementalImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
So the above approach actually worked very well and produce fairly smooth lines like so but the user was always locked into using 4 quadrants because they were separate UIView's:
After some thinking we decided to scrap the 4 separate UIView's and use a single view to handle the drawing which would allow an arbitrary number of lines to be drawn at a time giving the user more options (8 lines for example), and this is where things got tricky.
The MainViewController no longer handles the touches interaction methods, the new 'DrawingView' captures the gestures itself with a UILongPressGestureRecogniser.
func handleLongPressDrawing(sender: UILongPressGestureRecognizer) {
var p = sender.locationInView(self)
switch sender.state {
case UIGestureRecognizerState.Began:
self.began(p)
break;
case UIGestureRecognizerState.Changed:
self.moved(p)
break;
case UIGestureRecognizerState.Ended:
self.ended(p)
default:
break;
}
}
The methods now reference a new DrawingElement class to perform the symmetry calculations:
enum GridType {
case ONE, TWO_1, TWO_2, TWO_3, TWO_4, THREE, FOUR_1, FOUR_2, FIVE, SIX_1, SIX_2, SEVEN, EIGHT_1, SIXTEEN
}
enum DrawingElementType {
case PATH, POINT, CIRCLE
}
class DrawingElement: NSObject {
var points : [CGPoint] = []
private var drawingWidth : CGFloat!
private var drawingHeight : CGFloat!
private var gridType : GridType!
private var drawingElementType : DrawingElementType!
init(gridType : GridType, drawingWidth : CGFloat, drawingHeight : CGFloat) {
self.gridType = gridType
self.drawingWidth = drawingWidth
self.drawingHeight = drawingHeight
super.init()
}
func getPoints() -> [CGPoint] {
return points
}
func addPoint(pointCG: CGPoint) {
points.append(pointCG)
}
func getPoint(pos : Int) -> CGPoint {
return points[pos]
}
func getDrawingWidth() -> CGFloat {
return drawingWidth
}
func setDrawingWidth(w : CGFloat) {
drawingWidth = w
}
func getDrawingWidthCG() -> CGFloat {
return CGFloat(drawingWidth)
}
func getDrawingHeight() -> CGFloat {
return drawingHeight
}
func setDrawingHeight(h : CGFloat) {
drawingHeight = h
}
func getDrawingHeightCG() -> CGFloat {
return CGFloat(drawingHeight)
}
func getPointCount() -> Int {
return points.count
}
func getDrawingElementType() -> DrawingElementType {
return drawingElementType
}
func setDrawingElementType(det : DrawingElementType) {
drawingElementType = det
}
func getGridType() -> GridType {
return gridType
}
func setGridType(gt : GridType) {
gridType = gt
}
func smoothLinesPart1() {
points[2] = CGPointMake((points[1].x + points[3].x)/2.0, (points[1].y + points[3].y)/2.0)
}
func smoothLinesMoveTo() -> CGPoint {
return points[0]
}
func smoothLinesQuadCurve() -> (CGPoint, CGPoint) {
return (points[2], points[1])
}
func smoothLinesReorderArray() {
points[0] = points[2]
points[1] = points[3]
}
func getCalculatedPoints(allPoints : [CGPoint]) -> [Int : [CGPoint]] {
var newPoints = [CGPoint]()
var numberOfPoints : Int!
var temp : CGFloat!
var x : CGFloat!
var y : CGFloat!
//println("Before Path points: \(allPoints)")
var pathPoints = [Int() : [CGPoint]()]
if(gridType == GridType.EIGHT_1) {
numberOfPoints = 8
} else if(gridType == GridType.ONE) {
numberOfPoints = 1
} else if(gridType == GridType.TWO_1) {
numberOfPoints = 2
} else if(gridType == GridType.FOUR_1) {
numberOfPoints = 4
}
var firstTime = true
for point in allPoints {
x = point.x
y = point.y
if(gridType == GridType.EIGHT_1 || gridType == GridType.ONE || gridType == GridType.TWO_1 || gridType == GridType.FOUR_1) {
if(firstTime) {
for i in 1...numberOfPoints {
switch (i) {
case 5:
temp = y;
y = x;
x = temp;
pathPoints[4] = [CGPoint(x: x, y: y)]
case 1:
pathPoints[0] = [CGPoint(x: x, y: y)]
//println(" first point\(pathPoints[0])")
break;
case 2:
pathPoints[1] = [CGPoint(x: (x - getDrawingWidthCG()) * -1, y: y)]
break;
case 6:
pathPoints[5] = [CGPoint(x: (x - getDrawingWidthCG()) * -1, y: y)]
break;
case 3:
pathPoints[2] = [CGPoint(x: x, y: (y - getDrawingHeightCG()) * -1)]
break;
case 7:
pathPoints[6] = [CGPoint(x: x, y: (y - getDrawingHeightCG()) * -1)]
break;
case 4:
pathPoints[3] = [CGPoint(x: (x - getDrawingWidthCG()) * -1, y: (y - getDrawingHeightCG()) * -1)]
break;
case 8:
pathPoints[7] = [CGPoint(x: (x - getDrawingWidthCG()) * -1, y: (y - getDrawingHeightCG()) * -1)]
break;
default:
break
//newPoints.append(CGPoint(x: x, y: y))
}
}
firstTime = false
} else {
for i in 1...numberOfPoints {
switch (i) {
case 5:
temp = y;
y = x;
x = temp;
pathPoints[4]?.append(CGPoint(x: x, y: y))
case 1:
pathPoints[0]?.append(CGPoint(x: x, y: y))
//println(" first point\(pathPoints[0])")
break;
case 2:
pathPoints[1]?.append(CGPoint(x: (x - getDrawingWidthCG()) * -1, y: y))
break;
case 6:
pathPoints[5]?.append(CGPoint(x: (x - getDrawingWidthCG()) * -1, y: y))
break;
case 3:
pathPoints[2]?.append(CGPoint(x: x, y: (y - getDrawingHeightCG()) * -1))
break;
case 7:
pathPoints[6]?.append(CGPoint(x: x, y: (y - getDrawingHeightCG()) * -1))
break;
case 4:
pathPoints[3]?.append(CGPoint(x: (x - getDrawingWidthCG()) * -1, y: (y - getDrawingHeightCG()) * -1))
break;
case 8:
pathPoints[7]?.append(CGPoint(x: (x - getDrawingWidthCG()) * -1, y: (y - getDrawingHeightCG()) * -1))
break;
default:
break
//newPoints.append(CGPoint(x: x, y: y))
}
}
}
}
}
}
And this is called at various parts of the DrawingViews interaction handlers:
var paths = [Int() : UIBezierPath()]
func began (beganPoint: CGPoint) {
strokes = 0
var p = beganPoint
ctr = 0
//pts.insert(beganPoint, atIndex: 0)
drawingElement?.addPoint(beganPoint)
}
func moved(movedPoints: CGPoint) {
strokes++
var p = movedPoints
ctr++
drawingElement?.addPoint(movedPoints)
if ctr == 3 {
drawingElement?.smoothLinesPart1()
path.moveToPoint(drawingElement!.smoothLinesMoveTo())
path.addQuadCurveToPoint(drawingElement!.smoothLinesQuadCurve().0, controlPoint: drawingElement!.smoothLinesQuadCurve().1)
self.setNeedsDisplay()
drawingElement?.smoothLinesReorderArray()
ctr = 1
}
var pointsArray : [CGPoint] = drawingElement!.getPoints()
var calcArray = drawingElement?.getCalculatedPoints(pointsArray)
let sortedCalcArray = sorted(calcArray!) { $0.0 < $1.0 }
if pointsArray.count > 1 {
for (pIndex, path) in sortedCalcArray {
paths[pIndex] = UIBezierPath()
for var i = 0; i < path.count; i++ {
paths[pIndex]!.moveToPoint(path[i])
if(i > 0) {
paths[pIndex]!.addLineToPoint(path[i-1])
}
self.setNeedsDisplay()
}
}
}
override func drawRect(rect: CGRect) {
for (index, path) in paths {
path.lineCapStyle = kCGLineCapRound
path.lineWidth = lineWidth
color.setStroke()
path.stroke()
}
color.setStroke()
incrementalImage.drawInRect(rect)
}
}
I have a feeling that either 1) The iPhone does like drawing 4 or more paths within a single view at a time, or 2) the performance is degraded because of the number of loops that are running each time the user moves their finger. Here is what a similar line looks like with the above new code:
So after all of that I am wondering if anyone would be able to shed some light on why the new code draws so differently or what a better approach may be.
Thanks
So after some trial and error I kept most of the code above, the only notable difference was that I constructed 4 separate UIBezierPaths and eliminated the for loop nested in the other for loop. That seemed to be causing the issue

Resources