Search code examples
iosuiviewswift3core-graphics

How to change fillColor of UIGraphicsGetCurrentContext?


I've written this custom UIView class to draw custom shape

class ShapeView: UIView {

var color: UIColor?

init(frame: CGRect, color: UIColor) {
    super.init(frame: frame)
    self.color = color
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

override func draw(_ rect: CGRect) {
    super.draw(rect)
    drawShape(rect: rect, color: self.color!)
}

func drawShape(rect: CGRect,color: UIColor) {
    guard let ctx = UIGraphicsGetCurrentContext() else { return }
    ctx.setFillColor(UIColor.clear.cgColor)

    ctx.beginPath()
    ctx.move(to: CGPoint(x: rect.width / 2, y: 0))
    ctx.addLine(to: CGPoint(x: rect.width, y: rect.height / 2))
    ctx.addLine(to: CGPoint(x: rect.width / 2 , y: rect.height))
    ctx.addLine(to: CGPoint(x: 0, y: rect.height / 2))


    ctx.closePath()
    ctx.setFillColor(color.cgColor)
    ctx.fillPath()
    ctx.strokePath()
}

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    let path = UIBezierPath(ovalIn: self.frame)
    return path.contains(point)
}

and when the user pressed somewhere inside the shape I need to change the fillColor of this shape to let's say black with this code // while debugging the color didn't change it's look like I did something wrong.

in some UIViewController class I wrote this method

  class SomeClass: UIViewController {
   var shape1: ShapeView?
   var frame: CGRect?

  override func viewDidLoad() {
   let x = view.frame.midX
    let y = view.frame.midY

    self.frame = CGRect(x: x, y: y, width: 100, height: 100)
   super.viewDidLoad()
   self.shape1 = ShapeView(frame: frame!, color: .red)
    shape1?.backgroundColor = .clear
    view.addSubview(shape1!)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let location = touches.first!.location(in: self.view)
    if (shape1?.point(inside: location, with: event))! {
        print("inside shape1")
        shape1?.drawShape(rect: frame!, color: .black)
    } else {
        print("outside shape1")
        shape1?.drawShape(rect: frame!, color: .red)
    }
  }

any ideas !


Solution

  • Don't call your drawShape() func directly - it gets called every time the object paints itself:

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        drawShape(rect: rect, color: self.color!)
    }
    

    So, it will immediately change the fill color back to shape1's color property.

    Instead, do something like this (not tested, just typing here):

    if (shape1?.point(inside: location, with: event))! {
        print("inside shape1")
        shape1?.color = UIColor.black
    } else {
        print("outside shape1")
        shape1?.color = UIColor.red
    }
    shape1?.setNeedsDisplay()
    

    Edit: You can also modify your ShapeView class like this:

    var color: UIColor? {
        didSet {
            self.setNeedsDisplay()
        }
    }
    

    which would eliminate the need to "manually" call .setNeedsDisplay() in your touches handler.

    Edit #2: You can paste this into a Playground page and it should run without changes...

    import UIKit
    import PlaygroundSupport
    
    class ShapeView: UIView {
    
        var color: UIColor? {
            didSet {
                self.setNeedsDisplay()
            }
        }
    
        init(frame: CGRect, color: UIColor) {
            super.init(frame: frame)
            self.color = color
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override func draw(_ rect: CGRect) {
            super.draw(rect)
            drawShape(rect: rect, color: self.color!)
        }
    
        func drawShape(rect: CGRect,color: UIColor) {
            guard let ctx = UIGraphicsGetCurrentContext() else { return }
            ctx.setFillColor(UIColor.clear.cgColor)
    
            ctx.beginPath()
            ctx.move(to: CGPoint(x: rect.width / 2, y: 0))
            ctx.addLine(to: CGPoint(x: rect.width, y: rect.height / 2))
            ctx.addLine(to: CGPoint(x: rect.width / 2 , y: rect.height))
            ctx.addLine(to: CGPoint(x: 0, y: rect.height / 2))
    
    
            ctx.closePath()
            ctx.setFillColor(color.cgColor)
            ctx.fillPath()
            ctx.strokePath()
        }
    
        override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
            let path = UIBezierPath(ovalIn: self.frame)
            return path.contains(point)
        }
    }
    
    class VCA : UIViewController {
    
        var shape1: ShapeView?
        var frame: CGRect?
    
        override func viewDidLoad() {
            let x = view.frame.midX
            let y = view.frame.midY
    
            self.frame = CGRect(x: x, y: y, width: 100, height: 100)
            super.viewDidLoad()
            self.shape1 = ShapeView(frame: frame!, color: .red)
            shape1?.backgroundColor = .clear
            view.addSubview(shape1!)
        }
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            let location = touches.first!.location(in: self.view)
            if (shape1?.point(inside: location, with: event))! {
                print("inside shape1")
                shape1?.color = UIColor.black
            } else {
                print("outside shape1")
                shape1?.color = UIColor.red
            }
        }
    
    }
    
    let vcA = VCA()
    vcA.view.backgroundColor = .yellow
    PlaygroundPage.current.liveView = vcA.view
    

    Screen-Recording of result running in a playground page:

    enter image description here