0

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
}
Bibbs1000
  • 23
  • 3
  • listArray is update by your function so it needs to be a @State property. You can’t call a function directly from inside your view code like this, call it from a view modifier. Your function returns a value you are not using. Etc etc. – Joakim Danielson Feb 27 '23 at 08:13
  • can you show the code for `midpoint` and `isTooSmall` – workingdog support Ukraine Feb 27 '23 at 08:43
  • don't understand your question at all. You want recursion, but then you don't want it to calculate anything, just drawing, but then again you do want recursion but not using it to calculate the array of points ahead of time? – workingdog support Ukraine Feb 27 '23 at 23:57

0 Answers0