Drawing UIBezierPaths inside existing path's coordinates or use CGRect?

I have a subview that I have placed inside my ViewController via IB. Inside that view, I want to draw a progress tracker composed of circles and lines using UIBezierPaths. In my subview, I do not want to draw the progress tracker edge to edge, instead, I want to have a nice visual padding... To do that, I did several things:


1. Made a SizeRatios struct with constants that I developed to help me define the size of my circles, lines, and padding depending on the subviews height and width


2. My next approach was to make a rectangular bezier path inside my subview (in this path, the progress tracker components will be draw in and bounded by the path. I have several questions:


* Is my strategy so far in the right direction (drawing bezier paths inside an existing path)?

* How do I access the coordinate space of the path and not the subview, I want to draw inside the path?

* Would making a CGRect and drawing inside the corrdinate space of that be a good solution?


I am specifically stuck on my makeElement() method, the points are based on the rectangular bezier paths coordinates, but the circle is being draw in the subview, I want to draw inside the rectangualar bezier path... Thanks in advance!!! 🙂


Screenshot Link: h ttps://drive.google.com/file/d/1kM6nmyIiOtyNjB3B3NQOgwzXxdkLMCmw/view?usp=sharing


import UIKit


class ProgressTrackerView: UIView {
   
   
   // Array for holding circular bezier paths
   var paths = [UIBezierPath()]
   
   
   // Number of circles to make, this will be later determined
   // by some automatic measn based on some server logic... for simplicity sake, this will
   // not go above 4.
   var numberOfElements = 4
   
   
   // Stores stride points... see calculateStridePoints() method.
   // In a nutshell, stride points are used to draw circular bezier paths
   // inside the rectangular path.
   var stridePoints = [CGPoint()]
   
   
   // The bezier path that will be drawn into, possibly, maybe a CGRect?
   var progressTrackerRect: UIBezierPath = UIBezierPath()
   
   
   /** Computes and returns the `bounds.width` value of the `ProgressTrackerView`. */
   var progressTrackerViewWidth: CGFloat {
      
      return bounds.width
      
   }
   
   
   
   /** Computes and returns the `bounds.height` value of the `ProgressTrackerView`. */
   var progressTrackerViewHeight: CGFloat {
      
      return bounds.height
      
   }
   
   
   
   // Size Ratios
   // These were determined by doing some quick mockups in Keynote
   private struct SizeRatios {
      
      
      /** Ratio to set the width of the `progressTrackerRect` to be proportional to the width of the `ProgressTrackerView` */
      static let progressTrackerRectWidthToViewWidth: CGFloat = 0.885
      
      
      /** Ratio to set the height of a progress element proportional to height of the `ProgressTrackerView` */
      static let progressElementHeightToViewHeight: CGFloat = 0.531
      
      
      /** Ratio to set the height of a line proportional to the height of a progress element */
      static let lineHeightToProgressElementHeight: CGFloat = 0.250
      
      
   }
   
   
   
   
   
   /** Configures and returns a rectangular bezier path. The progress tracker (composes of circular elements and lines) will be drawn in the bounds
    of this bezier path.*/
   func prepareRectForProgressTracker() -> UIBezierPath {
      
      
      /** Define the width and height of the progress tracker rectangle. */
      let progressTrackerRectWidth = progressTrackerViewWidth * SizeRatios.progressTrackerRectWidthToViewWidth
      let progressTrackerRectHeight = progressTrackerViewHeight * SizeRatios.progressElementHeightToViewHeight
      
      
      /** Define the x and y coordinates of the rectangle */
      let xRect = (progressTrackerViewWidth - progressTrackerRectWidth) / 2
      let yRect = (progressTrackerViewHeight - progressTrackerRectHeight) / 2
      
      
      /** The final rectangle to be returned */
      let progressTrackerRectPath = UIBezierPath(rect: CGRect(x: xRect, y: yRect, width: progressTrackerRectWidth, height: progressTrackerRectHeight))
      
      progressTrackerRectPath.lineWidth = 2.0
      
      
      progressTrackerRect = progressTrackerRectPath
      
      return progressTrackerRect
      
      
      
   }
   
   
   
   
   func calculateStridePoints() {
      
      let subtractedValue = progressTrackerRect.bounds.width / CGFloat(numberOfElements * 2)
      
      let strideByConstant = progressTrackerRect.bounds.width / CGFloat(numberOfElements)
      
      for strideValue in stride(from: strideByConstant, to: progressTrackerRect.bounds.width + strideByConstant, by: strideByConstant) {
         
         let stridePoint = CGPoint(x: strideValue - subtractedValue, y: progressTrackerRect.bounds.midY)
         
         stridePoints.append(stridePoint)
         
         
         
         print(stridePoint)
         
      }
      
   }
   
   
   
   // This is where I am stuck... trying to draw only one path for now for testing purposes...
   // but how to i make sure that the arc center point is a poin in the rectangular bezier path and not the view...
   func makeElementAt() -> UIBezierPath {
      
      
      
      
      let circlePath = UIBezierPath(arcCenter: stridePoints[0],
                                    radius: (progressTrackerRect.bounds.height / 2),
                                    startAngle: 0.0,
                                    endAngle: CGFloat(2*Double.pi),
                                    clockwise: false)
      
      
      
      return circlePath
      
      
   }
   
   
   

   
   
   
   override func draw(_ rect: CGRect) {
      
      
      
      let color = UIColor.red
      color.setStroke()
      
      let progressTrackerRect = prepareRectForProgressTracker()
      progressTrackerRect.stroke()
      
      
      
      
      
      /// I know bad coding practice, drawRect is only for drawing, I'm just
      /// doing this to get the fundamentals working and will move this outta
      /// drawRect..
      calculateStridePoints()
      
      let circlePathColor = UIColor.blue
      circlePathColor.setStroke()
      circlePathColor.setFill()
      
      let testCirclePath = makeElementAt()
      testCirclePath.stroke()
      testCirclePath.fill()
      
      
   }
}
Answered by Claude31 in 330094022

It draws correctly in portrait, but outside the framing rect in landscape...if I start the app in landscape, then it draws it incorrectlt in the framing rect when switched to portrait.



I tested, it works exactly the same in landscape or portrait.

Could you post a link to a screen capture showing the problem ?


A personal comment ; I find the func calculateStridePoints very difficult to read. For something very simple:

func calculateStridePoints() {
      let subtractedValue = progressTrackerRect.width / CGFloat(numberOfElements * 2)
      let strideByConstant = progressTrackerRect.width / CGFloat(numberOfElements)
      for strideValue in stride(from: strideByConstant, to: progressTrackerRect.width + strideByConstant, by: strideByConstant) {
         let stridePoint = CGPoint(x: strideValue - subtractedValue, y: progressTrackerRect.midY)
         stridePoints.append(stridePoint)
         print(stridePoint)
      }
   }

- you want to calculte the center for numberOfElements circles,

- spaced by strideByConstant

- starting at strideByConstant - subtractedValue which equals progressTrackerRect.width / CGFloat(numberOfElements * 2)

Note: this value is relative to the view, not to the progressRect ; you should start from

progressTrackerRect.minX + progressTrackerRect.width / CGFloat(numberOfElements * 2)


So, I would write

  func calculateStridePoints() {
        var xPos = progressTrackerRect.minX + progressTrackerRect.width / CGFloat(numberOfElements * 2)     // The inital value
        let spacing = progressTrackerRect.width / CGFloat(numberOfElements)

        for _ in 0..<numberOfElements {         // The editor may have hidden <numberOfElements
           let stridePoint = CGPoint(x: xPos, y: progressTrackerRect.midY)
           stridePoints.append(stridePoint)
           xPos += spacing
      }
   }


To get the call to this method out of draw, you can make stridePoints a computed var

var stridePoints : [CGPoint] {
     return calculateStridePoints()
}



By having calculateStridePoints return [CGPoints]

    func calculateStridePoints() -> [CGPoint] {
        var thePoints = [CGPoint]()
        var xPos = progressTrackerRect.minX + progressTrackerRect.width / CGFloat(numberOfElements * 2)     // The inital value
        let spacing = progressTrackerRect.width / CGFloat(numberOfElements)
 
       for _ in 0..<numberOfElements {  // This editor sometimes hides the end of line <numberOfElements {
            let stridePoint = CGPoint(x: xPos, y: progressTrackerRect.midY)
            thePoints.append(stridePoint)
            xPos += spacing
        }
        return thePoints
    }

Or, to avoid calling calculate repeatidly, put the calculate in the init

Forward

// but how to i make sure that the arc center point is a point in the rectangular bezier path and not the view...


I don't really understand the problem.



- You know the rectangular bezier path CGRect ?

- You know stridePoints[0]



So, why not test that stridePoints[0] is indide rectangular bezier path CGRect ? And return an empty bezier path if not ?

The stride points were calculated based on the bounds of the rectangular bezier path, however, when i try to make a circle with one of the stride points, it doesnt draw it in the rectangular path, instead it draws it in the gray view that the rectangular path sits in...


Heres the printout of the stride points from the debugger...However, when I use these points to draw a circle, it thinks that that point is in the subview, where in actuality, I want to be in the coordinate space of the rectangular path... hopefully that makes sense, please let me know if it doesnt..

(45.79875, 59.5)

(137.39625, 59.5)

(228.99375, 59.5)

(320.59125, 59.5)

Just re-read you post... I tried something like this...

// This is where I am stuck... trying to draw only one path for now for testing purposes...
   // but how to i make sure that the arc center point is a poin in the rectangular bezier path and not the view...
   func makeElementAt(point: CGPoint) {
      
     
      if progressTrackerRect.contains(point) {
         
        print("Yes")
         
      } else {
         
         print("No")
         
         
      }
   
      
      
   }


Then I called it in drawRect line 24:


override func draw(_ rect: CGRect) {
      
      
      
      let color = UIColor.red
      color.setStroke()
      
      let progressTrackerRect = prepareRectForProgressTracker()
      progressTrackerRect.stroke()
      
      
      
      
      
      /// I know bad coding practice, drawRect is only for drawing, I'm just
      /// doing this to get the fundamentals working and will move this outta
      /// drawRect..
      calculateStridePoints()
      
      let circlePathColor = UIColor.blue
      circlePathColor.setStroke()
      circlePathColor.setFill()
      
      makeElementAt()

}


Debugger Output

(45.79875, 59.5)

(137.39625, 59.5)

(228.99375, 59.5)

(320.59125, 59.5)

no



Okay.. made some progress.. looks like its working.. somewhat... I stil think its drawing in the rectangular bezier path... if I switch from potrait to landscape the rectangular path resizes as expected.. however the circle is drawn outside the rect.. See screenshot...


h ttps://drive.google.com/file/d/1RlRG7_RKaFM1sCNd4S-RdwuquaIOCF3X/view?usp=sharing


// This is where I am stuck... trying to draw only one path for now for testing purposes...
   // but how to i make sure that the arc center point is a poin in the rectangular bezier path and not the view...
   func makeElementAt(point: CGPoint) -> UIBezierPath {
      
      var path = UIBezierPath()
      
      if progressTrackerRect.contains(point) {
         
         
         path = UIBezierPath(arcCenter: point,
                             radius: 20,
                             startAngle: 0.0,
                             endAngle: CGFloat(2 * Double.pi), clockwise: false)
      
      
      
   }
      
      return path
      
   }



All of the code for referecne..


import UIKit


class ProgressTrackerView: UIView {
   
   
   // Array for holding circular bezier paths
   var paths = [UIBezierPath()]
   
   
   var somePath = UIBezierPath()
   
   // Number of circles to make, this will be later determined
   // by some automatic measn based on some server logic... for simplicity sake, this will
   // not go above 4.
   var numberOfElements = 4
   
   
   // Stores stride points... see calculateStridePoints() method.
   // In a nutshell, stride points are used to draw circular bezier paths
   // inside the rectangular path.
   var stridePoints = [CGPoint()]
   
   
   // The bezier path that will be drawn into, possibly, maybe a CGRect?
   var progressTrackerRect: UIBezierPath = UIBezierPath()
   
   
   /** Computes and returns the `bounds.width` value of the `ProgressTrackerView`. */
   var progressTrackerViewWidth: CGFloat {
      
      return bounds.width
      
   }
   
   
   
   /** Computes and returns the `bounds.height` value of the `ProgressTrackerView`. */
   var progressTrackerViewHeight: CGFloat {
      
      return bounds.height
      
   }
   
   
   
   // Size Ratios
   // These were determined by doing some quick mockups in Keynote
   private struct SizeRatios {
      
      
      /** Ratio to set the width of the `progressTrackerRect` to be proportional to the width of the `ProgressTrackerView` */
      static let progressTrackerRectWidthToViewWidth: CGFloat = 0.885
      
      
      /** Ratio to set the height of a progress element proportional to height of the `ProgressTrackerView` */
      static let progressElementHeightToViewHeight: CGFloat = 0.531
      
      
      /** Ratio to set the height of a line proportional to the height of a progress element */
      static let lineHeightToProgressElementHeight: CGFloat = 0.250
      
      
   }
   
   
   
   
   
   /** Configures and returns a rectangular bezier path. The progress tracker (composes of circular elements and lines) will be drawn in the bounds
    of this bezier path.*/
   func prepareRectForProgressTracker() -> UIBezierPath {
      
      
      /** Define the width and height of the progress tracker rectangle. */
      let progressTrackerRectWidth = progressTrackerViewWidth * SizeRatios.progressTrackerRectWidthToViewWidth
      let progressTrackerRectHeight = progressTrackerViewHeight * SizeRatios.progressElementHeightToViewHeight
      
      
      /** Define the x and y coordinates of the rectangle */
      let xRect = (progressTrackerViewWidth - progressTrackerRectWidth) / 2
      let yRect = (progressTrackerViewHeight - progressTrackerRectHeight) / 2
      
      
      /** The final rectangle to be returned */
      let progressTrackerRectPath = UIBezierPath(rect: CGRect(x: xRect, y: yRect, width: progressTrackerRectWidth, height: progressTrackerRectHeight))
      
      progressTrackerRectPath.lineWidth = 2.0
      
      
      progressTrackerRect = progressTrackerRectPath
      
      return progressTrackerRect
      
      
      
   }
   
   
   
   
   func calculateStridePoints() {
      
      let subtractedValue = progressTrackerRect.bounds.width / CGFloat(numberOfElements * 2)
      
      let strideByConstant = progressTrackerRect.bounds.width / CGFloat(numberOfElements)
      
      for strideValue in stride(from: strideByConstant, to: progressTrackerRect.bounds.width + strideByConstant, by: strideByConstant) {
         
         let stridePoint = CGPoint(x: strideValue - subtractedValue, y: progressTrackerRect.bounds.midY)
         
         stridePoints.append(stridePoint)
         
         
         
         print(stridePoint)
         
      }
      
   }
   
   
   
   // This is where I am stuck... trying to draw only one path for now for testing purposes...
   // but how to i make sure that the arc center point is a poin in the rectangular bezier path and not the view...
   func makeElementAt(point: CGPoint) -> UIBezierPath {
      
      var path = UIBezierPath()
      
      if progressTrackerRect.contains(point) {
         
         
         path = UIBezierPath(arcCenter: point,
                             radius: 20,
                             startAngle: 0.0,
                             endAngle: CGFloat(2 * Double.pi), clockwise: false)
         
         
         
      }
      
      return path
      
   }
   
   
   

   
   
   
   override func draw(_ rect: CGRect) {
      
      
      
      let color = UIColor.red
      color.setStroke()
      
      let progressTrackerRect = prepareRectForProgressTracker()
      progressTrackerRect.stroke()
      
      
      
      
      
      /// I know bad coding practice, drawRect is only for drawing, I'm just
      /// doing this to get the fundamentals working and will move this outta
      /// drawRect..
      calculateStridePoints()
      

      
      let circlePathColor = UIColor.blue
      circlePathColor.setStroke()
      circlePathColor.setFill()
      
     
      let testCirclePath = makeElementAt(point: stridePoints[1])
      testCirclePath.stroke()
      testCirclePath.fill()
      
      
      
   }
}

I ran your code.


The first mistake is line 20.

You write:

    var stridePoints = [CGPoint()]


Hence, you create an array with one element, initialized as 0, 0

So, when you call stridePoints[0], you get it.


You should write instead:

    var stridePoints = [CGPoint]()


to initialize an empty array.


Other remarks:

- naming a path as progressTrackerRect is really a bad idea, because it is not a rect.


you could instead declare as a CGRect and initialize :

        progressTrackerRect = CGRect(x: xRect, y: yRect, width: progressTrackerRectWidth, height: progressTrackerRectHeight)
        let progressTrackerRectPath = UIBezierPath(rect: progressTrackerRect)
  
        progressTrackerRectPath.lineWidth = 2.0
  
        // Delete this progressTrackerRect = progressTrackerRectPath

Of course, you will have to change

progressTrackerRect.bounds.

as

progressTrackerRect.

Oh 😮... Thanks for catching that... just fixed that... And about your suggestion about the CGRect, I will defintly fix that, I agree with you on that. But how do I draw in the coordinate space of the CGRect... would I use UICoordinate space?

What do you get now ? Does it draw correctly or outside of the framing rect ?


But how do I draw in the coordinate space of the CGRect... would I use UICoordinate space?


A simple way is to offset everything with origin of the view.

This can be done with

func convert(_ point: CGPoint, from view: UIView?) -> CGPoint


that you apply to the instance of ProgressTrackerView


But I guess that should not be needed once you have modified with a Rect.

Working on this right now and I ran into a question... If i am making a CGRect, does making a bezier path out of the rect make sense... the only reason I am making a rect or bezier path that is rectangular is to draw my progress tracker into a sort of bounding box for visual padding... would just having a CGRect do the trick?

It draws correctly in portrait, but outside the framing rect in landscape...if I start the app in landscape, then it draws it incorrectlt in the framing rect when switched to portrait..


Here is the prepareProgressTrackerRect method:


func prepareRectForProgressTracker() -> UIBezierPath {
      
      
      /** Define the width and height of the progress tracker rectangle. */
      let progressTrackerRectWidth = progressTrackerViewWidth * SizeRatios.progressTrackerRectWidthToViewWidth
      let progressTrackerRectHeight = progressTrackerViewHeight * SizeRatios.progressElementHeightToViewHeight
      
      
      /** Define the x and y coordinates of the rectangle */
      let xRect = (progressTrackerViewWidth - progressTrackerRectWidth) / 2
      let yRect = (progressTrackerViewHeight - progressTrackerRectHeight) / 2
      
      
      progressTrackerRect = CGRect(x: xRect,
                                   y: yRect,
                                   width: progressTrackerRectWidth,
                                   height: progressTrackerRectHeight)
      
      /** The final rectangle to be returned */
      let progressTrackerRectPath = UIBezierPath(rect: progressTrackerRect)
      
      progressTrackerRectPath.lineWidth = 2.0
      
      return progressTrackerRectPath
      
   
      
      
      
   }



Here is makeElementAt(point:) method

func makeElementAt(point: CGPoint) -> UIBezierPath {
      
      var path = UIBezierPath()
      
      if progressTrackerRect.contains(point) {
         
         
         path = UIBezierPath(arcCenter: point,
                             radius: 20,
                             startAngle: 0.0,
                             endAngle: CGFloat(2 * Double.pi), clockwise: false)
         
         
         
      }
      
      return path
      
   }
  



Heres the whole class...


import UIKit


class ProgressTrackerView: UIView {
   
   
   // Array for holding circular bezier paths
   var paths = [UIBezierPath()]
   
   
   var somePath = UIBezierPath()
   
   // Number of circles to make, this will be later determined
   // by some automatic measn based on some server logic... for simplicity sake, this will
   // not go above 4.
   var numberOfElements = 4
   
   
   // Stores stride points... see calculateStridePoints() method.
   // In a nutshell, stride points are used to draw circular bezier paths
   // inside the rectangular path.
   var stridePoints = [CGPoint]()
   
   
   // The bezier path that will be drawn into, possibly, maybe a CGRect?
   var progressTrackerRect: CGRect = CGRect()
   
   
   /** Computes and returns the `bounds.width` value of the `ProgressTrackerView`. */
   var progressTrackerViewWidth: CGFloat {
      
      return bounds.width
      
   }
   
   
   
   /** Computes and returns the `bounds.height` value of the `ProgressTrackerView`. */
   var progressTrackerViewHeight: CGFloat {
      
      return bounds.height
      
   }
   
   
   
   // Size Ratios
   // These were determined by doing some quick mockups in Keynote
   private struct SizeRatios {
      
      
      /** Ratio to set the width of the `progressTrackerRect` to be proportional to the width of the `ProgressTrackerView` */
      static let progressTrackerRectWidthToViewWidth: CGFloat = 0.885
      
      
      /** Ratio to set the height of a progress element proportional to height of the `ProgressTrackerView` */
      static let progressElementHeightToViewHeight: CGFloat = 0.531
      
      
      /** Ratio to set the height of a line proportional to the height of a progress element */
      static let lineHeightToProgressElementHeight: CGFloat = 0.250
      
      
   }
   
   
   
   
   
   /** Configures and returns a rectangular bezier path. The progress tracker (composes of circular elements and lines) will be drawn in the bounds
    of this bezier path.*/
   func prepareRectForProgressTracker() -> UIBezierPath {
      
      
      /** Define the width and height of the progress tracker rectangle. */
      let progressTrackerRectWidth = progressTrackerViewWidth * SizeRatios.progressTrackerRectWidthToViewWidth
      let progressTrackerRectHeight = progressTrackerViewHeight * SizeRatios.progressElementHeightToViewHeight
      
      
      /** Define the x and y coordinates of the rectangle */
      let xRect = (progressTrackerViewWidth - progressTrackerRectWidth) / 2
      let yRect = (progressTrackerViewHeight - progressTrackerRectHeight) / 2
      
      
      progressTrackerRect = CGRect(x: xRect,
                                   y: yRect,
                                   width: progressTrackerRectWidth,
                                   height: progressTrackerRectHeight)
      
      /** The final rectangle to be returned */
      let progressTrackerRectPath = UIBezierPath(rect: progressTrackerRect)
      
      progressTrackerRectPath.lineWidth = 2.0
      
      return progressTrackerRectPath
      
   
      
      
      
   }
   
   
   
   
   func calculateStridePoints() {
      
      let subtractedValue = progressTrackerRect.width / CGFloat(numberOfElements * 2)
      
      let strideByConstant = progressTrackerRect.width / CGFloat(numberOfElements)
      
      for strideValue in stride(from: strideByConstant, to: progressTrackerRect.width + strideByConstant, by: strideByConstant) {
         
         let stridePoint = CGPoint(x: strideValue - subtractedValue, y: progressTrackerRect.midY)
         
         stridePoints.append(stridePoint)
         
         
         
         print(stridePoint)
         
      }
      
   }
   
   
   
   // This is where I am stuck... trying to draw only one path for now for testing purposes...
   // but how to i make sure that the arc center point is a poin in the rectangular bezier path and not the view...
   func makeElementAt(point: CGPoint) -> UIBezierPath {
      
      var path = UIBezierPath()
      
      if progressTrackerRect.contains(point) {
         
         
         path = UIBezierPath(arcCenter: point,
                             radius: 20,
                             startAngle: 0.0,
                             endAngle: CGFloat(2 * Double.pi), clockwise: false)
         
         
         
      }
      
      return path
      
   }
   
   
   

   
   
   
   override func draw(_ rect: CGRect) {
      
      
      
      let color = UIColor.red
      color.setStroke()
      
      let progressTrackerRect = prepareRectForProgressTracker()
      progressTrackerRect.stroke()
      
      
      
      
      
      /// I know bad coding practice, drawRect is only for drawing, I'm just
      /// doing this to get the fundamentals working and will move this outta
      /// drawRect..
      calculateStridePoints()
      

      
      let circlePathColor = UIColor.blue
      circlePathColor.setStroke()
      circlePathColor.setFill()
      
     
      let testCirclePath = makeElementAt(point: stridePoints[0])
      testCirclePath.stroke()
      testCirclePath.fill()
      
      
      
   }
}
Accepted Answer

It draws correctly in portrait, but outside the framing rect in landscape...if I start the app in landscape, then it draws it incorrectlt in the framing rect when switched to portrait.



I tested, it works exactly the same in landscape or portrait.

Could you post a link to a screen capture showing the problem ?


A personal comment ; I find the func calculateStridePoints very difficult to read. For something very simple:

func calculateStridePoints() {
      let subtractedValue = progressTrackerRect.width / CGFloat(numberOfElements * 2)
      let strideByConstant = progressTrackerRect.width / CGFloat(numberOfElements)
      for strideValue in stride(from: strideByConstant, to: progressTrackerRect.width + strideByConstant, by: strideByConstant) {
         let stridePoint = CGPoint(x: strideValue - subtractedValue, y: progressTrackerRect.midY)
         stridePoints.append(stridePoint)
         print(stridePoint)
      }
   }

- you want to calculte the center for numberOfElements circles,

- spaced by strideByConstant

- starting at strideByConstant - subtractedValue which equals progressTrackerRect.width / CGFloat(numberOfElements * 2)

Note: this value is relative to the view, not to the progressRect ; you should start from

progressTrackerRect.minX + progressTrackerRect.width / CGFloat(numberOfElements * 2)


So, I would write

  func calculateStridePoints() {
        var xPos = progressTrackerRect.minX + progressTrackerRect.width / CGFloat(numberOfElements * 2)     // The inital value
        let spacing = progressTrackerRect.width / CGFloat(numberOfElements)

        for _ in 0..<numberOfElements {         // The editor may have hidden <numberOfElements
           let stridePoint = CGPoint(x: xPos, y: progressTrackerRect.midY)
           stridePoints.append(stridePoint)
           xPos += spacing
      }
   }


To get the call to this method out of draw, you can make stridePoints a computed var

var stridePoints : [CGPoint] {
     return calculateStridePoints()
}



By having calculateStridePoints return [CGPoints]

    func calculateStridePoints() -> [CGPoint] {
        var thePoints = [CGPoint]()
        var xPos = progressTrackerRect.minX + progressTrackerRect.width / CGFloat(numberOfElements * 2)     // The inital value
        let spacing = progressTrackerRect.width / CGFloat(numberOfElements)
 
       for _ in 0..<numberOfElements {  // This editor sometimes hides the end of line <numberOfElements {
            let stridePoint = CGPoint(x: xPos, y: progressTrackerRect.midY)
            thePoints.append(stridePoint)
            xPos += spacing
        }
        return thePoints
    }

Or, to avoid calling calculate repeatidly, put the calculate in the init

I tested, it works exactly the same in landscape or portrait.

Could you post a link to a screen capture showing the problem?


Still having issuess...

Please see this small 30 second video I posted on Google Drive.... It shows both scenarios of landscape and portrait.

h ttps://drive.google.com/file/d/18FLCZB5yTGA2cN6qcdKsXWIiBlveQGBf/view?usp=sharing


A personal comment ; I find the func calculateStridePoints very difficult to read. For something very simple:


100% agree with you on this, and that was one of my next tasks this week... I just wrote up something quick to understand what was going on



To get the call to this method out of draw, you can make stridePoints a computed var


This is awesome! Thanks for that tip. Will implement for sure today!

I think the problem comes from how you calculate the first value for stride.


Test with what I proposed (including progressTrackerRect.minX):


func calculateStridePoints() {

var xPos = progressTrackerRect.minX + progressTrackerRect.width / CGFloat(numberOfElements * 2) // The inital value

let spacing = progressTrackerRect.width / CGFloat(numberOfElements)


for _ in 0..<numberOfElements { // The editor may have hidden <numberOfElements

let stridePoint = CGPoint(x: xPos, y: progressTrackerRect.midY)

stridePoints.append(stridePoint)

xPos += spacing

}

}

Okay, let me test it out and get back to you...

Okay... its working!!! I dont quite understand why it worked with your implementation of calculateStridePoints... was it becasue I was using Swift's stride method or using the method with the incorrect starting point?

Drawing UIBezierPaths inside existing path's coordinates or use CGRect?
 
 
Q