Solution for Ch 18 Silver, and Gold Challenge

Silver Challenge: Colors

Changes to Line.swift: import UIKit and make extension to Line

// import Foundation
import UIKit
import CoreGraphics

struct Line {
  var begin = CGPoint.zero
  var end = CGPoint.zero
}

extension Line {
  var angle: Measurement<UnitAngle> {
    guard begin != end else {
      return Measurement(value: 0.0, unit: .radians)
    }
    let dy = Double(end.y - begin.y)
    let dx = Double(end.x - begin.x)
    let angleInRadian: Measurement<UnitAngle> = Measurement(value: atan2(dy, dx), unit: .radians)
    return angleInRadian
  }
  
  var color: UIColor {
    let colors = [ UIColor.black, UIColor.blue, UIColor.brown, UIColor.cyan, UIColor.darkGray, UIColor.gray, UIColor.green, UIColor.lightGray, UIColor.magenta, UIColor.orange, UIColor.purple, UIColor.red, UIColor.yellow]
    let ratio = (self.angle.value + Double.pi) / (Double.pi * 2)   // First map angle in 0 ..< 2*Pi
    let colorIndex = Int( Double(colors.count - 1) * ratio)
    return colors[colorIndex]
  }
}

Update draw(_:slight_smile: in DrawView.swift

override func draw(_ rect: CGRect) {
  for line in finishedLines {
    line.color.setStroke()    // Use color by angle
    stroke(line)
  }
  
  currentLineColor.setStroke()
  for (_, line) in currentLines {
    line.color.setStroke()    // Use color by angle
    stroke(line)
  }
}

Gold Challenge: Circles

Two touches will trigger the draw circle mode. The center of circle is calculated by mid-point of the two touches.

Create a file Circle.swift

import CoreGraphics

struct Circle {
  var rect = CGRect.zero
  
  init() {
    self.rect = CGRect.zero
  }
  
  init(rect: CGRect) {
    self.rect = rect
  }
  
  init(point1: CGPoint, point2: CGPoint) {
    let width = abs(point2.x - point1.x)
    let height = abs(point2.y - point1.y)
    let diameter = min(width, height)
    let radius = diameter / 2
    let center = CGPoint(x: (point1.x + point2.x) / 2, y: (point1.y + point2.y) / 2)
    self.rect = CGRect(x: center.x - radius, y: center.y - radius, width: diameter, height: diameter)
  }
}

Here is updated DrawView.swift

import UIKit

class DrawView: UIView {

  var currentLines = [NSValue:Line]()
  var finishedLines = [Line]()
  
  var currentCircle = Circle()
  var finishedCircles = [Circle]()
  
  @IBInspectable var finishedLineColor: UIColor = UIColor.black {
    didSet {
      setNeedsDisplay()
    }
  }
  
  @IBInspectable var currentLineColor: UIColor = UIColor.red {
    didSet {
      setNeedsDisplay()
    }
  }
  @IBInspectable var lineThickness: CGFloat = 10 {
    didSet {
      setNeedsDisplay()
    }
  }

  func stroke(_ line: Line) {
    let path = UIBezierPath()
    path.lineWidth = lineThickness
    path.lineCapStyle = .round
    
    path.move(to: line.begin)
    path.addLine(to: line.end)
    path.stroke()
  }
    
  override func draw(_ rect: CGRect) {
    for line in finishedLines {
      line.color.setStroke()    // Use color by angle
      stroke(line)
    }
    
    currentLineColor.setStroke()
    for (_, line) in currentLines {
      line.color.setStroke()    // Use color by angle
      stroke(line)
    }
    
    // Draw Circles
    finishedLineColor.setStroke()
    for circle in finishedCircles {
      let path = UIBezierPath(ovalIn: circle.rect)
      path.lineWidth = lineThickness
      path.stroke()
    }
    currentLineColor.setStroke()
    UIBezierPath(ovalIn: currentCircle.rect).stroke()
  }
  
  override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    // Log statement to see the order of events
    print(#function)
    
    if touches.count == 2 {
      let touchesArray = Array(touches)
      let location1 = touchesArray[0].location(in: self)
      let location2 = touchesArray[1].location(in: self)
      currentCircle = Circle(point1: location1, point2: location2)
    } else {
      for touch in touches {
        let location = touch.location(in: self)
        
        let newline = Line(begin: location, end: location)
        
        let key = NSValue(nonretainedObject: touch)
        currentLines[key] = newline
      }
    }
    setNeedsDisplay()
  }
  
  override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    // Log statement to see the order of events
    print(#function)
    
    if touches.count == 2 {
      let touchesArray = Array(touches)
      let location1 = touchesArray[0].location(in: self)
      let location2 = touchesArray[1].location(in: self)
      currentCircle = Circle(point1: location1, point2: location2)
    } else {
      for touch in touches {
        let key = NSValue(nonretainedObject: touch)
        currentLines[key]?.end = touch.location(in: self)
      }
    }
    setNeedsDisplay()
  }
  
  override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    // Log statement to see the order of events
    print(#function)
    
    if touches.count == 2 {
      let touchesArray = Array(touches)
      let location1 = touchesArray[0].location(in: self)
      let location2 = touchesArray[1].location(in: self)
      currentCircle = Circle(point1: location1, point2: location2)
      finishedCircles.append(currentCircle)
      currentCircle = Circle()
    } else {
      for touch in touches {
        let key = NSValue(nonretainedObject: touch)
        if var line = currentLines[key] {
          line.end = touch.location(in: self)
          
          finishedLines.append(line)
          currentLines.removeValue(forKey: key)
        }
      }
    }
    setNeedsDisplay()
  }
  
  override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
    // Log statement to see the order of events
    print(#function)
    
    currentLines.removeAll()
    currentCircle = Circle()
    
    setNeedsDisplay()
  }
}
1 Like

Thanks for the solution.

To have more colors I did this:

in Line.swift

extension Line {
    
    var angleDegree : CGFloat {
        
        guard begin != end else { return 0 }
    
        let dX = end.x - begin.x
        let dY = end.y - begin.y
    
        var angle = atan2(dY, dX) * 180 / CGFloat(M_PI)
    
        //make negative angles be positive and angles can go from 0 to 360
        if angle < 0 {
            angle = angle + 360
        }
        print(angle)
        return CGFloat(angle)
    
    }
    
    var color : UIColor {
        let hueCode = angleDegree / 360
        
        return UIColor(hue: hueCode, saturation: 1, brightness: 1, alpha: 0.6)
    }
  
}

That way there is a lot of colors depending on the angle.

Gold Challenge: Circles

@hkray, this approach isn’t working reliably. Using more than 2 fingers can start creating lines as well as circles simultaneously. I am looking for a better approach and will post it when I get it.

Yes, the source code is buggy. The cases when a user changes fingers from/to 2 are not elaborated.