 # 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(_ 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.location(in: self)
let location2 = touchesArray.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.location(in: self)
let location2 = touchesArray.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.location(in: self)
let location2 = touchesArray.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.