I am a Java programmer trying to learn the ways of Swift. A few days ago, I posted the code for a very simple app that draws a Sierpinski triangle and implements a pretty buggy zoom. I was told to work on organization, delegation, and creating global functions, all of which I tried to do. I also added some new features in the form of a popover view that allows the user to control the number of triangles drawn and the colors they are drawn in.
I am looking for feedback in how I did with my organization, delegation, and global function and if there is anything I can make more "Swift-y" in its implementation.
SierpinskiView
import UIKit
protocol SierpinskiViewDataSource: class {
func NumberTrianglesinSierpinskiTriangle(sender: SierpinskiView) -> Int?
func BackgroundColorForSierpinskiTriangle(sender: SierpinskiView) -> UIColor?
func MainColorForSierpinskiTriangle(sender: SierpinskiView) -> UIColor?
}
func midPoint(point1: CGFloat, point2: CGFloat) -> CGFloat {
return (point1 + point2) / 2
}
@IBDesignable
class SierpinskiView: UIView {
//MARK: - Properties
var scale : CGFloat = 0.95
var triangleTipLocation : CGFloat = 100 {
didSet {
if triangleTipLocation < 0 {
triangleTipLocation = 0
}
if triangleTipLocation>100 {
triangleTipLocation = 100
}
setNeedsDisplay()
}
}
weak var delegate = SierpinskiViewDataSource?()
//MARK - Life Cycle
override func drawRect(rect: CGRect) {
draw()
}
//MARK - Drawing Functions
private func draw() {
let bigTriangleRightX : CGFloat = bounds.size.width * scale
let bigTriangleLeftX : CGFloat = bounds.size.width - bigTriangleRightX
let triangleHeight : CGFloat = triangleTipLocation + sqrt(((midPoint(bigTriangleLeftX, point2: bigTriangleRightX) - bigTriangleLeftX)*((midPoint(bigTriangleLeftX, point2: bigTriangleRightX) - bigTriangleLeftX)) * 3))
let triangleTop = CGPoint(x: midPoint(bigTriangleLeftX, point2: bigTriangleRightX), y: triangleTipLocation)
let rightTriangleCorner = CGPoint(x: bigTriangleRightX, y: triangleHeight)
let leftTriangleCorner = CGPoint(x: bigTriangleLeftX, y: triangleHeight)
let backColor : UIColor = delegate?.BackgroundColorForSierpinskiTriangle(self) ?? UIColor.blackColor()
drawTriangle(leftTriangleCorner, right: rightTriangleCorner, mid: triangleTop, color: backColor)
drawWhiteTriangle(leftTriangleCorner, rightPoint: rightTriangleCorner, topPoint: triangleTop)
}
private func drawWhiteTriangle(leftPoint: CGPoint, rightPoint: CGPoint, topPoint: CGPoint) {
let middle = midPoint(leftPoint.x, point2: rightPoint.x)
let leftCorner = CGPoint(x: midPoint(middle, point2: leftPoint.x), y: midPoint(leftPoint.y, point2: topPoint.y))
let rightCorner = CGPoint(x: midPoint(middle, point2: rightPoint.x), y: midPoint(rightPoint.y, point2: topPoint.y))
let midCorner = CGPoint(x: middle, y: leftPoint.y)
let mainColor : UIColor = delegate?.MainColorForSierpinskiTriangle(self) ?? UIColor.whiteColor()
let numberTrianglesToDraw = delegate?.NumberTrianglesinSierpinskiTriangle(self) ?? 0
drawTriangle(leftCorner, right: rightCorner, mid: midCorner, color: mainColor)
drawRecursiveTriangles(leftCorner, rightPoint: rightCorner, middlePoint: midCorner, numTriangles: numberTrianglesToDraw)
}
private func drawRecursiveTriangles(leftPoint: CGPoint, rightPoint: CGPoint, middlePoint: CGPoint, numTriangles: Int) {
if numTriangles==0 {
return
}
let mainColor : UIColor = delegate?.MainColorForSierpinskiTriangle(self) ?? UIColor.whiteColor()
let leftLeftCorner = CGPoint(x: leftPoint.x - (midPoint(leftPoint.x, point2: middlePoint.x)-leftPoint.x), y: midPoint(middlePoint.y, point2: leftPoint.y))
let leftRightCorner = CGPoint(x: midPoint(middlePoint.x, point2: leftPoint.x), y: midPoint(middlePoint.y, point2: leftPoint.y))
let leftMidCorner = CGPoint(x: midPoint(leftLeftCorner.x, point2: leftRightCorner.x), y: middlePoint.y)
drawTriangle(leftLeftCorner, right: leftRightCorner, mid: leftMidCorner, color: mainColor)
drawRecursiveTriangles(leftLeftCorner, rightPoint: leftRightCorner, middlePoint: leftMidCorner, numTriangles: numTriangles-1)
let rightLeftCorner = CGPoint(x: midPoint(rightPoint.x, point2: middlePoint.x), y: midPoint(leftPoint.y, point2: middlePoint.y))
let rightRightCorner = CGPoint(x: rightPoint.x + (rightPoint.x-midPoint(middlePoint.x, point2: rightPoint.x)), y: midPoint(leftPoint.y, point2: middlePoint.y))
let rightMidCorner = CGPoint(x: midPoint(rightLeftCorner.x, point2:rightRightCorner.x), y: middlePoint.y)
drawTriangle(rightLeftCorner, right: rightRightCorner, mid: rightMidCorner, color: mainColor)
drawRecursiveTriangles(rightLeftCorner, rightPoint: rightRightCorner, middlePoint: rightMidCorner, numTriangles: numTriangles-1)
let midLeftCorner = CGPoint(x: midPoint(middlePoint.x, point2: leftPoint.x), y: leftPoint.y - (middlePoint.y-midPoint(leftPoint.y, point2: middlePoint.y)))
let midRightCorner = CGPoint(x: midPoint(middlePoint.x, point2: rightPoint.x), y: leftPoint.y - (middlePoint.y-midPoint(leftPoint.y, point2: middlePoint.y)))
let midMidCorner = CGPoint(x: midPoint(leftPoint.x, point2: rightPoint.x), y: leftPoint.y)
drawTriangle(midLeftCorner, right: midRightCorner, mid: midMidCorner, color: mainColor)
drawRecursiveTriangles(midLeftCorner, rightPoint: midRightCorner, middlePoint: midMidCorner, numTriangles: numTriangles-1)
}
private func drawTriangle(left: CGPoint, right: CGPoint, mid: CGPoint, color: UIColor) {
color.set()
let path = UIBezierPath()
path.moveToPoint(left)
path.addLineToPoint(right)
path.addLineToPoint(mid)
path.fill()
path.stroke()
}
}
SierpinskiViewController
import UIKit
protocol SierpinskiController: class {
func NumberTrianglesinSierpinskiTriangle(sender: SierpinskiViewController) -> Int?
func BackgroundColorForSierpinskiTriangle(sender: SierpinskiViewController) -> UIColor
func MainColorForSierpinskiTriangle(sender: SierpinskiViewController) -> UIColor
func updateUI(sender: SierpinskiViewController)
}
class SierpinskiViewController: UIViewController, UIScrollViewDelegate, SierpinskiViewDataSource, UIPopoverPresentationControllerDelegate {
//MARK: - Properties
@IBOutlet weak var scrollView: UIScrollView!
{
didSet {
scrollView.delegate = self
scrollView.maximumZoomScale = 20.0
scrollView.minimumZoomScale = 1.0
}
}
@IBOutlet weak var sierpinskiView: SierpinskiView! {
didSet {
sierpinskiView.delegate = self
}
}
weak var delegate = SierpinskiController?()
//MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
scrollView.addSubview(sierpinskiView)
let widthConstant = CGFloat(100)
let heightConstant = CGFloat(100)
scrollView.contentSize = CGSize(width: sierpinskiView.frame.width + widthConstant , height: sierpinskiView.frame.height + heightConstant)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
switch identifier {
case "controlsSegue":
if let cvc = segue.destinationViewController as? ControlsViewController {
if let ppc = cvc.popoverPresentationController {
ppc.delegate = self
}
cvc.sierpinskiViewController = self
}
default: break
}
}
}
//MARK: - View Setting Functions
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return sierpinskiView
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return UIModalPresentationStyle.None
}
//MARK: - SierpinskiView Protocol Functions
func updateUI () {
sierpinskiView.setNeedsDisplay()
}
func NumberTrianglesinSierpinskiTriangle(sender: SierpinskiView) -> Int? {
return delegate?.NumberTrianglesinSierpinskiTriangle(self) ?? 0
}
func BackgroundColorForSierpinskiTriangle(sender: SierpinskiView) -> UIColor? {
return delegate?.BackgroundColorForSierpinskiTriangle(self) ?? UIColor.blackColor()
}
func MainColorForSierpinskiTriangle(sender: SierpinskiView) -> UIColor? {
return delegate?.MainColorForSierpinskiTriangle(self) ?? UIColor.whiteColor()
}
}
ControlsViewController
import UIKit
class ControlsViewController: UIViewController, SierpinskiController {
//MARK: - Properties
@IBOutlet weak var controlsView: UIView!
@IBOutlet weak var BackgroundColorButton: UIButton!
@IBOutlet weak var MainColorButton: UIButton!
@IBOutlet weak var numberOfTrianglesLabel: UILabel!
@IBOutlet weak var numberOfTrianglesSlider: UISlider!
private var mainColorSelected = false {
didSet {
switch mainColorSelected {
case true: MainColorButton.setTitleColor(UIColor.redColor(), forState: .Normal)
BackgroundColorButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
case false: BackgroundColorButton.setTitleColor(UIColor.redColor(), forState: .Normal)
MainColorButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
}
}
}
var sierpinskiViewController = SierpinskiViewController?() {
didSet {
sierpinskiViewController?.delegate = self
updateUI(sierpinskiViewController!)
}
}
var mainColor : UIColor = UIColor.whiteColor() {
didSet {
updateUI(sierpinskiViewController!)
}
}
var backgroundColor: UIColor = UIColor.blackColor() {
didSet {
updateUI(sierpinskiViewController!)
}
}
var numberTriangles : Int = 0 {
didSet {
updateUI(sierpinskiViewController!)
}
}
//MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
mainColorSelected = false
}
//MARK: - SierpinskiView Setting Functions
@IBAction func backgroundColorSelected(sender: UIButton) {
mainColorSelected = false
}
@IBAction func mainColorSelected(sender: UIButton) {
mainColorSelected = true
}
@IBAction func changeNumberOfTriangles(sender: UISlider) {
let currentValue = Int(sender.value)
numberOfTrianglesLabel.text = "\(currentValue)"
numberTriangles = currentValue
}
@IBAction func changeColor(sender: AnyObject) {
let color = sender.currentTitle!
switch mainColorSelected {
case true:
switch color! {
case "R": mainColor = UIColor.redColor()
case "O": mainColor = UIColor.orangeColor()
case "Y": mainColor = UIColor.yellowColor()
case "G": mainColor = UIColor.greenColor()
case "B": mainColor = UIColor.blueColor()
case "P": mainColor = UIColor.purpleColor()
case "Bl": mainColor = UIColor.blackColor()
case "W": mainColor = UIColor.whiteColor()
default: break
}
case false:
switch color! {
case "R": backgroundColor = UIColor.redColor()
case "O": backgroundColor = UIColor.orangeColor()
case "Y": backgroundColor = UIColor.yellowColor()
case "G": backgroundColor = UIColor.greenColor()
case "B": backgroundColor = UIColor.blueColor()
case "P": backgroundColor = UIColor.purpleColor()
case "Bl": backgroundColor = UIColor.blackColor()
case "W": backgroundColor = UIColor.whiteColor()
default: break
}
}
}
//MARK: - SierpinskiController Protocol Functions
func NumberTrianglesinSierpinskiTriangle(sender: SierpinskiViewController) -> Int? {
return numberTriangles
}
func MainColorForSierpinskiTriangle(sender: SierpinskiViewController) -> UIColor {
return mainColor
}
func BackgroundColorForSierpinskiTriangle(sender: SierpinskiViewController) -> UIColor {
return backgroundColor
}
func updateUI(sender: SierpinskiViewController) {
sender.updateUI()
}
}