I've been trying to learn Swift as a hobby. I've been progressing fairly well, however I've been writing my programs using the command line tool template which makes it quick and easy but also means that I have not learned about views and graphics.
I was working through Al Sweigart's excellent Recursive Book of Recursion and got to a chapter on drawing fractals. Now I was in trouble because this would require graphics. I am really struggling trying to understand exactly what a "View" is and how to repeatly draw into ContentView. Specifically, I'm trying to draw a Sierpinski Triangle fractal which recursively draws smaller triangles within triangles. I have a function that calculates the points to draw the triangle sides between and this function recursively calls itself on each of the smaller triangles until the triangles get too small to draw.
I cannot for the life of me figure out how to call a function recursively from ContentView. Everything I try gives me an error message "Cannot conform to View"
I did at least get a drawing to work using a really clugey work around. Instead of recursively calculating on the fly, I instead created an array using a global array variable that has the first 20000 answers from the recursion (called listArray in the below) Then I just went through the array using ForEach and drew the points and lines set out for each item. I do get the correct picture drawn, but not very elegantly. Also, I had to arbitrarily decide to generate the first 20000 answers from the function when ideally I would want the program to keep going forever until the size of the triangles to be drawn was too small (i.e. less than a few CGPoints)
I've tried every search I can think of on stackoverflow and could not find a similar question. Note that @State variables would not work here (I believe) because I do not want to redraw the screen from scratch each time. I want the lines created from the previous iterations to remain on screen to that I continue to fill in the triangles into a more and more dense image.
Here is my ContentView code:
struct ContentView: View {
var ax = 50.0
var ay = 50.0
var bx = 350.0
var by = 650.0
var cx = 650.0
var cy = 50.0
var body: some View {
ZStack {
let temp = drawTriangle(ax: ax, ay: ay, bx: bx, by: by, cx: cx, cy: cy)
ForEach(listArray, id: \.self) { item in
Path() {path in
path.move(to: CGPoint(x: item[0],y: item[1]))
path.addLine(to: CGPoint(x: item[2],y:item[3]))
path.move(to: CGPoint(x: item[2],y:item[3]))
path.addLine(to: CGPoint(x: item[4],y:item[5]))
path.move(to: CGPoint(x:item[4],y:item[5]))
path.addLine(to: CGPoint(x:item[0],y:item[1]))
}
.stroke(Color.blue)
}
.frame(width: 700,height: 700)
}
}
}
drawTriangle is the function that I'd like to call recursively and each time draw the path from the points generated. Right now with my clugey code I've create an unused variable "temp", the purpose of which is to run drawTriangle once and I've hacked up drawTriangle to create 20000 items in array called listArray.
Here is the drawTriangle function:
func drawTriangle(ax: Double, ay: Double, bx: Double, by: Double, cx: Double, cy: Double)->[Double] {
if counter > 20000 {return [ax,ay,bx,by,cx,cy]}
if isTooSmall(ax: ax, ay: ay, bx: bx, by: by, cx: cx, cy: cy) {
//base case
return ([0,0,0,0,0,0])
} else {
//recursive case
//calculate midpoints between points A,B,C
let mid_ab = midpoint(startx: ax, starty: ay, endx: bx, endy: by)
let mid_bc = midpoint(startx: bx, starty: by, endx: cx, endy: cy)
let mid_ca = midpoint(startx: cx, starty: cy, endx: ax, endy: ay)
//draw the tree inner triangles
listArray.append(drawTriangle(ax: ax, ay: ay, bx: mid_ab.0, by: mid_ab.1, cx: mid_ca.0, cy: mid_ca.1))
listArray.append(drawTriangle(ax: mid_ab.0, ay: mid_ab.1, bx: bx, by: by, cx: mid_bc.0, cy: mid_bc.1))
listArray.append(drawTriangle(ax: mid_ca.0, ay: mid_ca.1, bx: mid_bc.0, by: mid_bc.1, cx: cx, cy: cy))
//print(listArray)
counter += 1
}
return ([ax,ay,bx,by,cx,cy])
}
var counter = 0
var listArray = [[Double]]()
This is so basic that it may be difficult for experienced Swift programers to know where to start. But if someone could lay out the general steps for me, or point me to another answer that does, I would really appreciate it.
Late addition: the other two functions referenced:
let min_size = 4.0 //try changing this to decrease/increase the amount of recursion
func midpoint(startx:Double, starty:Double, endx:Double, endy:Double)->(Double, Double) {
//return the x,y coordinate in the middle of the four given paramaters
let xDiff = abs(startx - endx)
let yDiff = abs(starty - endy)
return (min(startx,endx) + (xDiff / 2.0), min(starty,endy)+(yDiff/2.0))
}
func isTooSmall(ax: Double,ay:Double,bx:Double,by:Double,cx:Double,cy:Double)->Bool {
//determine if triangle is too small to draw
let width = max(ax,bx,cx) - min(ax,bx,cx)
let height = max(ay,by,cy) - min(ay,by,cy)
return width < min_size || height < min_size
}