iOS 核心图形动画

iOS Animation with Core Graphics

我目前正在 iOS 使用 Swift.

创建 Hangman 游戏应用程序

我已经完成了所有的游戏机制,想用 Core Graphics 来画刽子手和绞刑架。我可以使用 UIBezierPath 绘制绞架和刽子手,并将视图的每个部分(绞架、头部、body、左臂...)的绘制分解为单独的函数,调用这些函数来创建整个图像.所有这些都在自定义 UIView class 中。

我似乎无法弄清楚的是如何在用户做出错误猜测时添加 Hangman 图像的连续部分。我试图避免在一系列图像之间循环。相反,我想使用相同的自定义视图使用 Core Graphics 逐个绘制火柴人。

如何实现绘制图形的每个连续部分的函数?

类似于 "self.customView.updateDrawing()",其中 customView 中的 updateDrawing 方法将从模型中获取有关错误猜测次数的信息,并调用适当的方法来绘制简笔画所需的部分。

更新

这是我尝试将此 post https://codereview.stackexchange.com/questions/97424/hangman-in-swift 的视图应用到我自己的刽子手游戏中的尝试。它们非常适合绘制绞架,但实际上我无法让图像自行更新并绘制连续的 body 部分来添加刽子手。到目前为止,这是我的代码:

GameViewTwo:这是上面的 GameView post,我尝试将其拆分并将不同的部分作为层添加到视图中。

 import UIKit

enum BodyPart: Int {
    case Head = 1
    case Body = 2
    case LeftArm = 5
    case RightArm = 6
    case LeftLeg = 3
    case RightLeg = 4
    case LeftEye = 7
    case RightEye = 8
    case Mouth = 9
    case Unknown = 10
}


class GameViewTwo: UIView {

    var path = UIBezierPath()
    var newPartLayer = CAShapeLayer()

    private var bodyStart: CGPoint = CGPoint.zero
    private var bodyEnd: CGPoint = CGPoint.zero
    private var headMiddle: CGPoint = CGPoint.zero

    struct DrawingConstants {
        static let gallowBaseStartScale: CGFloat = 0.15
        static let gallowBaseEndScale: CGFloat = 0.85
        static let gallowBaseHeight: CGFloat = 10
        static let gallowHeight: CGFloat = 0.05        //static let gallowHeight: CGFloat = 0.15
        static let gallowHeightStart: CGFloat = 0.175
        static let gallowHeightWidth: CGFloat = 10
        static let gallowAcrossScale: CGFloat = 0.5
        static let gallowTipHeight: CGFloat = 17.5
        static let headRadius: CGFloat = 16
        static let bodyLength: CGFloat = 25
        static let bodyHeight: CGFloat = 25
        static let legLength: CGFloat = 50
        static let grassHeightScale: CGFloat = 0.68
        static let armBack: CGFloat = 5
    }

    struct ScaleConstants {
        static let bodyLength: CGFloat = 50
        static let limbLength: CGFloat = 25
        static let handHeightScale: CGFloat = 0.4
        static let headRadius: CGFloat = 20
        static let eyeRadius = CGFloat(0.15 * ScaleConstants.headRadius)
        static let eyeOffset = CGFloat(0.3 * ScaleConstants.headRadius)
        static let mouthOffSet = CGFloat(0.3 * ScaleConstants.headRadius)
        static let mouthRadius = CGFloat(0.25 * ScaleConstants.headRadius)
    }

    var part : BodyPart?

    func setPart(part: BodyPart){
        self.part = part
    }


    // Only override draw() if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func draw(_ rect: CGRect) {
        // Drawing code
        drawGallow()

    }


    func add(part: BodyPart){

        let partPath = path(forPart: part)
        newPartLayer.frame = bounds
        newPartLayer.path = partPath.cgPath
        newPartLayer.strokeColor = UIColor.black.cgColor
        let strokeAnim = CABasicAnimation(keyPath: "strokeEnd")
        strokeAnim.fromValue = 0
        strokeAnim.toValue = 1
        strokeAnim.duration = 1
        layer.addSublayer(newPartLayer)
        newPartLayer.add(strokeAnim, forKey: "path")

    }

    func path(forPart: BodyPart)-> UIBezierPath {

        switch forPart {

        case BodyPart.Head :
            let centerX = CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale - (DrawingConstants.gallowHeightWidth / 2))
            let centerY = CGFloat(bounds.size.height * DrawingConstants.gallowHeight + DrawingConstants.gallowBaseHeight + DrawingConstants.gallowTipHeight + ScaleConstants.headRadius)
            let center = CGPoint(x: centerX, y: centerY)
            headMiddle = center
            path = UIBezierPath(arcCenter: center, radius: ScaleConstants.headRadius, startAngle: CGFloat(0), endAngle: CGFloat(2 * M_PI), clockwise: true)
            path.lineWidth = CGFloat(2)

            return path

        case BodyPart.Body :
            let add = CGFloat(DrawingConstants.gallowBaseHeight + DrawingConstants.gallowTipHeight + 2 * ScaleConstants.headRadius)
            let startPointY = CGFloat(bounds.size.height * DrawingConstants.gallowHeight + add)
            let startPointX = CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale - (DrawingConstants.gallowHeightWidth / 2))
            let startPoint = CGPoint(x: startPointX, y: startPointY)
            let endPoint = CGPoint(x: startPoint.x, y: startPoint.y + ScaleConstants.bodyLength)
            bodyStart = startPoint
            bodyEnd = endPoint
            path.lineWidth = CGFloat(2)
            path.move(to: startPoint)
            path.addLine(to: endPoint)
            return path

        case BodyPart.LeftLeg :
            let startPoint = CGPoint(x: bodyEnd.x, y: bodyEnd.y)
            let endPoint = CGPoint(x: startPoint.x - ScaleConstants.limbLength, y: startPoint.y + ScaleConstants.limbLength)
            path.lineWidth = CGFloat(2)
            path.move(to: startPoint)
            path.addLine(to: endPoint)
            return path

        case BodyPart.RightLeg :
            let startPoint = CGPoint(x: bodyEnd.x, y: bodyEnd.y)
            let endPoint = CGPoint(x: startPoint.x + ScaleConstants.limbLength, y: startPoint.y + ScaleConstants.limbLength)
            path.lineWidth = CGFloat(2)
            path.move(to: startPoint)
            path.addLine(to: endPoint)
            return path

        case BodyPart.LeftArm :
            let startPoint = CGPoint(x: bodyStart.x, y: bodyStart.y + ScaleConstants.handHeightScale * ScaleConstants.bodyLength)
            let endPoint = CGPoint(x: startPoint.x - ScaleConstants.limbLength, y: startPoint.y - ScaleConstants.limbLength * ScaleConstants.handHeightScale)
            path.lineWidth = CGFloat(2)
            path.move(to: startPoint)
            path.addLine(to: endPoint)
            return path

        case BodyPart.RightArm :
            let startPoint = CGPoint(x: bodyStart.x, y: bodyStart.y + ScaleConstants.handHeightScale * ScaleConstants.bodyLength)
            let endPoint = CGPoint(x: startPoint.x + ScaleConstants.limbLength, y: startPoint.y - ScaleConstants.limbLength * ScaleConstants.handHeightScale)
            path.lineWidth = CGFloat(2)
            path.move(to: startPoint)
            path.addLine(to: endPoint)
            return path

        case BodyPart.LeftEye :
            UIColor.black.set()
            let eyeMiddle = CGPoint(x: headMiddle.x - ScaleConstants.eyeOffset, y: headMiddle.y - ScaleConstants.eyeOffset)

            let path = UIBezierPath(arcCenter: eyeMiddle, radius: ScaleConstants.eyeRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
            path.lineWidth = CGFloat(1)
            return path

        case BodyPart.RightEye :
            UIColor.black.set()
            let eyeMiddle = CGPoint(x: headMiddle.x + ScaleConstants.eyeOffset, y: headMiddle.y - ScaleConstants.eyeOffset)

            let path = UIBezierPath(arcCenter: eyeMiddle, radius: ScaleConstants.eyeRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
            path.lineWidth = CGFloat(1)
            return path

        default:
            return path
        }
    }

    /******************************************************************************************/

    func connectPoints(bottomLeftPoint: CGPoint, bottomRightPoint: CGPoint, topLeftPoint: CGPoint, topRightPoint: CGPoint, color: UIColor) {
        color.set()

        let path = UIBezierPath()
        path.move(to: bottomLeftPoint)
        path.addLine(to: topLeftPoint)
        path.addLine(to: topRightPoint)
        path.addLine(to: bottomRightPoint)
        path.close()
        path.fill()
        path.stroke()

    }

    func calculateMidPoint(point1: CGPoint, point2: CGPoint) -> CGPoint {
        return CGPoint(x: (point1.x + point2.x) / 2, y: (point1.y + point2.y) / 2)

    }

    /*****************************************************************************************/

    func drawGrass() {
        let topStartPoint = CGPoint(x: CGFloat(0), y: CGFloat(bounds.size.height * DrawingConstants.grassHeightScale))
        let topRightPoint = CGPoint(x: CGFloat(bounds.size.width), y: topStartPoint.y)
        let bottomRightPoint = CGPoint(x: topRightPoint.x, y: CGFloat(bounds.size.height))
        let bottomLeftPoint = CGPoint(x: CGFloat(0), y: bottomRightPoint.y)

        connectPoints(bottomLeftPoint: bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topStartPoint, topRightPoint: topRightPoint, color: UIColor.green)
    }

    func drawSky() {
        let bottomLeftPoint = CGPoint(x: CGFloat(0), y: CGFloat(bounds.size.height * DrawingConstants.grassHeightScale))
        let topLeftPoint = CGPoint(x: CGFloat(0), y: CGFloat(0))
        let topRightPoint = CGPoint(x: CGFloat(bounds.size.width), y: CGFloat(0))
        let bottomRightPoint = CGPoint(x: CGFloat(bounds.size.width), y: CGFloat(bounds.size.height * DrawingConstants.grassHeightScale))

        connectPoints(bottomLeftPoint: bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topLeftPoint, topRightPoint: topRightPoint, color: UIColor.cyan)
    }

    func drawGallow() {
        drawGallowBase()
        drawGallowHeight()
        drawGallowAcross()
        drawGallowTip()
    }

    func drawGallowBase() {
        let bottomLeftPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowBaseStartScale), y: CGFloat(bounds.size.height * DrawingConstants.grassHeightScale))
        let topLeftPoint = CGPoint(x: bottomLeftPoint.x, y: bottomLeftPoint.y - DrawingConstants.gallowBaseHeight)
        let topRightPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowBaseEndScale), y: topLeftPoint.y)
        let bottomRightPoint = CGPoint(x: topRightPoint.x, y: bottomLeftPoint.y)

        connectPoints(bottomLeftPoint: bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topLeftPoint, topRightPoint: topRightPoint, color: UIColor.brown)
    }

    func drawGallowHeight() {
        let bottomLeftPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowHeightStart), y: CGFloat(bounds.size.height * DrawingConstants.grassHeightScale - DrawingConstants.gallowBaseHeight))
        let bottomRightPoint = CGPoint(x: bottomLeftPoint.x + DrawingConstants.gallowHeightWidth, y: bottomLeftPoint.y)
        let topLeftPoint = CGPoint(x: bottomLeftPoint.x, y: bounds.size.height * DrawingConstants.gallowHeight)
        let topRightPoint = CGPoint(x: bottomRightPoint.x, y: topLeftPoint.y)

        connectPoints(bottomLeftPoint: bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topLeftPoint, topRightPoint: topRightPoint, color: UIColor.brown)
    }

    func drawGallowAcross() {
        let bottomLeftPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowHeightStart) + DrawingConstants.gallowHeightWidth, y: CGFloat(bounds.size.height * DrawingConstants.gallowHeight + DrawingConstants.gallowBaseHeight))
        let bottomRightPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale), y: bottomLeftPoint.y)
        let topLeftPoint = CGPoint(x: bottomLeftPoint.x, y: CGFloat(bounds.size.height * DrawingConstants.gallowHeight))
        let topRightPoint = CGPoint(x: CGFloat(bottomRightPoint.x), y: topLeftPoint.y)
        connectPoints(bottomLeftPoint: bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topLeftPoint, topRightPoint: topRightPoint, color: UIColor.brown)
    }

    func drawGallowTip() {
        let topLeftPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale - DrawingConstants.gallowHeightWidth), y: CGFloat(bounds.size.height * DrawingConstants.gallowHeight + DrawingConstants.gallowBaseHeight))
        let topRightPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale), y: topLeftPoint.y)
        let bottomLeftPoint = CGPoint(x: topLeftPoint.x, y: topLeftPoint.y + DrawingConstants.gallowTipHeight)
        let bottomRightPoint = CGPoint(x: topRightPoint.x, y: bottomLeftPoint.y)

        connectPoints(bottomLeftPoint: bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topLeftPoint, topRightPoint: topRightPoint, color: UIColor.brown)
    }

    /*******************************************************************************************/




}

然后像这样从我的 GameViewController 中调用它

let newPart = BodyPart(rawValue: (model?.attempts)!)

            self.hangmanView.add(part: newPart!)

其中 hangmanView 是一个 GameViewTwo

像这样的方法会奏效。有一个 属性 的自定义 HangmanView class 叫做 incorrectGuesses。更改 属性 将通过调用自身 setNeedsDisplay 来触发视图的重绘。

使用 switch 语句和 fallthough 将允许在 incorrectGuesses 增加时显示更多绘图。

class HangmanView: UIView {
    var incorrectGuesses = 0 {
        didSet {
            self.setNeedsDisplay()
        }
    }

    override func draw(_ rect: CGRect) {
        switch incorrectGuesses {
        case let x where x > 5:
            drawRightLeg()
            fallthrough
        case 5:
            drawLeftLeg()
            fallthrough
        case 4:
            drawRightArm()
            fallthrough
        case 3:
            drawLeftArm()
            fallthrough
        case 2:
            drawBody()
            fallthrough
        case 1:
            drawHead()
            fallthrough
        case 0:
            drawGallows()
        default:
            break
        }
    }

    func drawGallows() {
        // code to draw gallows
    }

    func drawHead() {
        // code to draw head
    }

    func drawBody() {
        // code to draw body
    }

    func drawLeftArm() {
        // code to draw left arm
    }

    func drawRightArm() {
        // code to draw right arm
    }

    func drawLeftLeg() {
        // code to draw left leg
    }

    func drawRightLeg() {
        // code to draw right leg
    }

}

要使用它,您可以在 ViewController 中为 HangmanView 设置一个出口,您只需设置 incorrectGuesses 来更新视图:

class ViewController: UIViewController {
    @IBOutlet weak var hangmanView: HangManView!
    var incorrectGuesses = 0

    func gameplay() {
        ...

        if letter not in word {
            incorrectGuesses += 1

            // Update the HangmanView
            hangmanView.incorrectGuesses = incorrectGuesses
        }
    }
}

在你的 Storyboard 中,添加一个 UIView 并在 Identity Inspector[ 中将 Class 设置为 HangmanView然后连接到插座上。


如果您想为 HangmanView 的绘图设置动画,您可以将 animationCount 属性 添加到 HangmanView。当它被设置为一个值时,例如 3,它将每隔 0.5 秒一点一点地动画绘图。要使用它,您需要设置 hangmanView.animationCount = 3.

var animationCount = 0 {
    didSet {
        incorrectGuesses = 0
        if animationCount > incorrectGuesses {
            _ = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
                self.incorrectGuesses += 1
                if self.incorrectGuesses >= self.animationCount {
                    timer.invalidate()
                }
            }
        }
    }
}

为每个 body 部分创建一个枚举。使它成为一个整数,这样你就可以用错误答案的数量来实例化它。

enum BodyPart: Int {
    case head = 0
    case leftArm
    case rightArm
...
}

制作新的 body 部分

let newPart = BodyPart(rawValue: incorrectGuesses - 1)

给你的视图几个函数...

func add(part: BodyPart){
    // first get a drawing path according to that part
    let partPath = path(for: part)

    // lets add a new layer so we can animate it seperately
    let newPartLayer = CAShapeLayer()
    newPartLayer.frame = bounds
    newPartLayer.path = partPath
    // linewidth, linecap you might want to play with

   let strokeAnim = CABasicAnimation(keyPath: "strokeEnd")
   strokeAnim.fromValue = 0
   strokeAnim.toValue = 1
   strokeAnim.duration = 0.5 // play with it, see what you like

   layer.addSublayer(layer: newPartLayer)
   newPartLayer.add(anim: strokeAnim)
}


func path(for: BodyPart) -> CGPath {

    // draw your part path, return it as a CGPath
}

然后从您的控制器中实例化一个新部件并将其添加到您的 hangmanview 中,它将按照您在绘图代码中绘制的方式绘制图层。 UIBezierPath 有一个 .cgPath 变量可以转换为核心图形路径。

let newPart = BodyPart(rawValue: incorrectGuesses - 1)
hangmanView.add(part: newPart)

当你想要一个新游戏时,只需从 hangmanView 中删除所有子层。 CAShapeLayers 对于简单的小形状非常有用,您可以动画更改路径(keyPath:"path")或在任一方向上抚摸路径。会好看的如果保留对所有零件层的引用,您甚至可以独立地移动零件或使用它们做其他有趣的事情。这样,您就有点遵守 model-view-controller 范例。您让游戏状态远离您的视图,您的所有视图都会添加 body 部分,并且您的控制器提供这样做的部分。简单的事情没什么大不了的,但是当你变得更好,事情变得更复杂时,记住这一点是很好的。

快速编辑:您可能想尝试 keyPath: "path",给它一个开始路径(可能是 body 路径)和一个结束路径,它看起来就像部分从body。祝你好运!

*** 已编辑答案。

我在这里清理了你的代码。我不完全确定为什么你在那里有某些变量或小 class 私有结构,但没关系,我把它们留在里面。我移动了一些东西但大部分保持不变,除了你很清楚的地方正在绘制一个矩形而不是其他形状。我没试过运行。让我知道进展如何。

enum BodyPart: Int {
case noose = 0
case head
case body
case leftLeg
case rightLeg
case leftArm
case rightArm
case leftEye
case rightEye
case mouth
case unknown
}




class HangmanView: UIView {


var partLayers = [CAShapeLayer]()
private var bodyStart: CGPoint = CGPoint.zero
private var bodyEnd: CGPoint = CGPoint.zero
private var headMiddle: CGPoint = CGPoint.zero

var currentPart: BodyPart?


func clear() {

    for i in partLayers {
        i.removeFromSuperlayer()
    }

    partLayers.removeAll()
}

func add(part: BodyPart){

    currentPart = part


    let newPartLayer = CAShapeLayer()

    let partPath = path(forPart: part)
    newPartLayer.frame = bounds
    newPartLayer.path = partPath.cgPath
    newPartLayer.strokeColor = UIColor.black.cgColor
    newPartLayer.fillColor = UIColor.clear.cgColor

    newPartLayer.lineCap = kCALineCapRound
    newPartLayer.lineWidth = part == .rightEye || part == .leftEye ? 1 : 2

    let strokeAnim = CABasicAnimation(keyPath: "strokeEnd")
    strokeAnim.fromValue = 0
    strokeAnim.toValue = 1
    strokeAnim.duration = 1
    layer.addSublayer(newPartLayer)
    newPartLayer.add(strokeAnim, forKey: "path")
    partLayers.append(newPartLayer)

}

func path(forPart: BodyPart) -> UIBezierPath {

    switch forPart {
    case .noose:
        return UIBezierPath()


    case .head :
        let centerX = CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale - (DrawingConstants.gallowHeightWidth / 2))
        let centerY = CGFloat(bounds.size.height * DrawingConstants.gallowHeight + DrawingConstants.gallowBaseHeight + DrawingConstants.gallowTipHeight + ScaleConstants.headRadius)
        let center = CGPoint(x: centerX, y: centerY)
        headMiddle = center
        let path = UIBezierPath(arcCenter: center, radius: ScaleConstants.headRadius, startAngle: CGFloat(0), endAngle: CGFloat(2 * M_PI), clockwise: true)

        return path

    case .body :
        let add = CGFloat(DrawingConstants.gallowBaseHeight + DrawingConstants.gallowTipHeight + 2 * ScaleConstants.headRadius)
        let startPointY = CGFloat(bounds.size.height * DrawingConstants.gallowHeight + add)
        let startPointX = CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale - (DrawingConstants.gallowHeightWidth / 2))
        let startPoint = CGPoint(x: startPointX, y: startPointY)
        let endPoint = CGPoint(x: startPoint.x, y: startPoint.y + ScaleConstants.bodyLength)
        bodyStart = startPoint
        bodyEnd = endPoint
        let path = UIBezierPath()
        path.move(to: startPoint)
        path.addLine(to: endPoint)
        return path

    case .leftLeg :
        let startPoint = CGPoint(x: bodyEnd.x, y: bodyEnd.y)
        let endPoint = CGPoint(x: startPoint.x - ScaleConstants.limbLength, y: startPoint.y + ScaleConstants.limbLength)
        let path = UIBezierPath()
        path.move(to: startPoint)
        path.addLine(to: endPoint)
        return path

    case .rightLeg :
        let startPoint = CGPoint(x: bodyEnd.x, y: bodyEnd.y)
        let endPoint = CGPoint(x: startPoint.x + ScaleConstants.limbLength, y: startPoint.y + ScaleConstants.limbLength)
        let path = UIBezierPath()
        path.move(to: startPoint)
        path.addLine(to: endPoint)
        return path

    case .leftArm :
        let startPoint = CGPoint(x: bodyStart.x, y: bodyStart.y + ScaleConstants.handHeightScale * ScaleConstants.bodyLength)
        let endPoint = CGPoint(x: startPoint.x - ScaleConstants.limbLength, y: startPoint.y - ScaleConstants.limbLength * ScaleConstants.handHeightScale)
        let path = UIBezierPath()
        path.move(to: startPoint)
        path.addLine(to: endPoint)
        return path

    case .rightArm :
        let startPoint = CGPoint(x: bodyStart.x, y: bodyStart.y + ScaleConstants.handHeightScale * ScaleConstants.bodyLength)
        let endPoint = CGPoint(x: startPoint.x + ScaleConstants.limbLength, y: startPoint.y - ScaleConstants.limbLength * ScaleConstants.handHeightScale)
        let path = UIBezierPath()
        path.move(to: startPoint)
        path.addLine(to: endPoint)
        return path

    case .leftEye :
        let eyeMiddle = CGPoint(x: headMiddle.x - ScaleConstants.eyeOffset, y: headMiddle.y - ScaleConstants.eyeOffset)

        let path = UIBezierPath(arcCenter: eyeMiddle, radius: ScaleConstants.eyeRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
        path.lineWidth = CGFloat(1)
        return path

    case .rightEye :
        let eyeMiddle = CGPoint(x: headMiddle.x + ScaleConstants.eyeOffset, y: headMiddle.y - ScaleConstants.eyeOffset)

        let path = UIBezierPath(arcCenter: eyeMiddle, radius: ScaleConstants.eyeRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
        path.lineWidth = CGFloat(1)
        return path

    default:
        return UIBezierPath()
    }
}




override func draw(_ rect: CGRect) {

    guard let context = UIGraphicsGetCurrentContext() else { return }
    // get the drawing context and save the ground state
    context.saveGState()

    // add sky and grass, now they are just rectangles
    context.setFillColor(UIColor.cyan.cgColor)
    context.fill(sky(rect))

    context.setFillColor(UIColor.green.cgColor)
    context.fill(grass(rect))

    context.setFillColor(UIColor.brown.cgColor)

    context.addPath(gallowBase(rect))
    context.addPath(gallowHeight(rect))
    context.addPath(gallowAcross(rect))
    context.addPath(gallowTip(rect))

    context.fillPath()
    context.restoreGState()

}



func gallowBase(_ rect: CGRect) -> CGPath {

    let bottomLeftPoint = CGPoint(x: CGFloat(rect.width * DrawingConstants.gallowBaseStartScale), y: CGFloat(rect.height * DrawingConstants.grassHeightScale))
    let topLeftPoint = CGPoint(x: bottomLeftPoint.x, y: bottomLeftPoint.y - DrawingConstants.gallowBaseHeight)
    let topRightPoint = CGPoint(x: CGFloat(rect.width * DrawingConstants.gallowBaseEndScale), y: topLeftPoint.y)
    let bottomRightPoint = CGPoint(x: topRightPoint.x, y: bottomLeftPoint.y)

    let path = CGMutablePath()
    path.addLines(between: [bottomLeftPoint,topLeftPoint, topRightPoint,bottomRightPoint])
    path.closeSubpath()
    return path
}

func gallowHeight(_ rect: CGRect)  -> CGPath {
    let bottomLeftPoint = CGPoint(x: CGFloat(rect.width * DrawingConstants.gallowHeightStart), y: CGFloat(rect.height * DrawingConstants.grassHeightScale - DrawingConstants.gallowBaseHeight))
    let bottomRightPoint = CGPoint(x: bottomLeftPoint.x + DrawingConstants.gallowHeightWidth, y: bottomLeftPoint.y)
    let topLeftPoint = CGPoint(x: bottomLeftPoint.x, y: rect.height * DrawingConstants.gallowHeight)
    let topRightPoint = CGPoint(x: bottomRightPoint.x, y: topLeftPoint.y)

    let path = CGMutablePath()
    path.addLines(between: [bottomLeftPoint,topLeftPoint, topRightPoint,bottomRightPoint])
    path.closeSubpath()
    return path
}

func gallowAcross(_ rect: CGRect)  -> CGPath {
    let bottomLeftPoint = CGPoint(x: CGFloat(rect.width * DrawingConstants.gallowHeightStart) + DrawingConstants.gallowHeightWidth, y: CGFloat(rect.height * DrawingConstants.gallowHeight + DrawingConstants.gallowBaseHeight))


    let bottomRightPoint = CGPoint(x: CGFloat(rect.width * DrawingConstants.gallowAcrossScale), y: bottomLeftPoint.y)
    let topLeftPoint = CGPoint(x: bottomLeftPoint.x, y: CGFloat(rect.height * DrawingConstants.gallowHeight))
    let topRightPoint = CGPoint(x: CGFloat(bottomRightPoint.x), y: topLeftPoint.y)

    let path = CGMutablePath()
    path.addLines(between: [bottomLeftPoint,topLeftPoint, topRightPoint,bottomRightPoint])
    path.closeSubpath()
    return path
}

func gallowTip(_ rect: CGRect)  -> CGPath {
    let topLeftPoint = CGPoint(x: CGFloat(rect.width * DrawingConstants.gallowAcrossScale - DrawingConstants.gallowHeightWidth), y: CGFloat(rect.height * DrawingConstants.gallowHeight + DrawingConstants.gallowBaseHeight))
    let topRightPoint = CGPoint(x: CGFloat(rect.width * DrawingConstants.gallowAcrossScale), y: topLeftPoint.y)
    let bottomLeftPoint = CGPoint(x: topLeftPoint.x, y: topLeftPoint.y + DrawingConstants.gallowTipHeight)
    let bottomRightPoint = CGPoint(x: topRightPoint.x, y: bottomLeftPoint.y)

    let path = CGMutablePath()
    path.addLines(between: [bottomLeftPoint,topLeftPoint, topRightPoint,bottomRightPoint])
    path.closeSubpath()
    return path
}



func grass(_ rect: CGRect)  -> CGRect {


    let grassRect = CGRect(x: 0, y: rect.height * DrawingConstants.grassHeightScale, width: rect.width, height: (1 - DrawingConstants.grassHeightScale) * rect.height)

    return grassRect
}

func sky(_ rect: CGRect)  -> CGRect {

    let skyRect = CGRect(x: 0, y: 0, width: rect.width, height: DrawingConstants.grassHeightScale * rect.height)

    return skyRect

}


func calculateMidPoint(point1: CGPoint, point2: CGPoint) -> CGPoint {
    return CGPoint(x: (point1.x + point2.x) / 2, y: (point1.y + point2.y) / 2)

}


struct DrawingConstants {
    static let gallowBaseStartScale: CGFloat = 0.15
    static let gallowBaseEndScale: CGFloat = 0.85
    static let gallowBaseHeight: CGFloat = 10
    static let gallowHeight: CGFloat = 0.05        //static let gallowHeight: CGFloat = 0.15
    static let gallowHeightStart: CGFloat = 0.175
    static let gallowHeightWidth: CGFloat = 10
    static let gallowAcrossScale: CGFloat = 0.5
    static let gallowTipHeight: CGFloat = 17.5
    static let headRadius: CGFloat = 16
    static let bodyLength: CGFloat = 25
    static let bodyHeight: CGFloat = 25
    static let legLength: CGFloat = 50
    static let grassHeightScale: CGFloat = 0.68
    static let armBack: CGFloat = 5
}

struct ScaleConstants {
    static let bodyLength: CGFloat = 50
    static let limbLength: CGFloat = 25
    static let handHeightScale: CGFloat = 0.4
    static let headRadius: CGFloat = 20
    static let eyeRadius = 0.15 * headRadius
    static let eyeOffset = 0.3 * headRadius
    static let mouthOffSet = 0.3 * headRadius
    static let mouthRadius = 0.25 * headRadius
}
}