Swift / UIView / drawrect - how to get drawrect to update when required
Asked Answered
W

2

13

I'm new to learning Swift, and am trying to get an incredibly simple app to run. All I'm trying to do is get UIView.drawRect to update when I press a button. It updates/draws when the app first loads, and then nothing after that, whatever I try. I've been hitting my head against this for a couple of days, and nothing I can find helps.

I created:

  • single view application

  • one button, linked to view controller as an action

  • a new class, Test_View, subclassing UIView

ViewController code:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        var f = Test_View()

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func Button_Pressed(sender: AnyObject) {
        var f = Test_View()
        f.setNeedsDisplay()
        NSLog("Button pressed.")
    }

}

Test_View code:

class Test_View: UIView {

    override func drawRect(rect: CGRect) {
        let h = rect.height
        let w = rect.width
        var color:UIColor = UIColor.yellowColor()

        var drect = CGRect(x: (w * 0.25),y: (h * 0.25),width: (w * 0.5),height: (h * 0.5))
        var bpath:UIBezierPath = UIBezierPath(rect: drect)

        color.set()
        bpath.stroke()

        NSLog("drawRect has updated the view")            

    }

}

(Note: the log is updated every time I press the button, so that's not the problem. It's just that the display never changes. Also, I've tried drawing the rectangle with random coordinates, so it's not that it's updating but I'm not seeing it.)

Thanks for any help!

Weller answered 28/12, 2014 at 16:57 Comment(0)
G
19

First, you need to specify a designated initializer for UIView (init with frame). Then make your object f a class constant or variable (depending on your need) so it can be accessed within the scope of the project. Also, you must add it as a subview of your view. It looks like this:

import UIKit

class ViewController: UIViewController {
    let f = Test_View(frame: CGRectMake(0, 0, 50, 50))

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(f)
    }

    @IBAction func buttonPressed(sender: UIButton) {
        f.setNeedsDisplay()
    }
}

class Test_View: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func draw(_ rect: CGRect) {
        let h = rect.height
        let w = rect.width
        let color:UIColor = UIColor.yellow

        let drect = CGRect(x: (w * 0.25),y: (h * 0.25),width: (w * 0.5),height: (h * 0.5))
        let bpath:UIBezierPath = UIBezierPath(rect: drect)

        color.set()
        bpath.stroke()

        NSLog("drawRect has updated the view")

    }

}
Glyoxaline answered 28/12, 2014 at 17:23 Comment(3)
Thank you for a very comprehensive answer! In retrospect, not declaring f like this seems so obvious! Interestingly, this implementation does have one quirk: the rectangle which appears upon load stays forever, while the rectangle which appears upon pressing the button appears until the next button press, whereupon it disappears and is redrawn. Presume this is down to the view/sub-view setup... will read up more!Weller
try adding super.drawRect(rect) to drawRect:Glyoxaline
Maybe it's late, and I never formally studied computer science, but when I read this paragraph: "Specify a designated initializer... Then make your object f a class constant ... so it can be accessed within the scope of the project. Also ... add it as a subview of your view". This sounds to me like gibberish. If you could rewrite this sentence as if you were explaining it to a child (me), how would you explain these instructions?Beulahbeuthel
M
3

You are calling setNeedsDisplay a local instance of Test_View instantiated in the Press_Button function, not one in you UIViewController (which you need to make by the way, creating it as a local variable in ViewDidLoad does you no good). You might want to create it as an @IBOutlet, then use interface builder to define an instance to attach to that variable - UIViewController looks as follows:

class ViewController: UIViewController {
    @IBOutlet weak var instruction: UILabel?
    @IBOutlet weak var f: Test_View?

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func Button_Pressed(sender: AnyObject) {
        f.setNeedsDisplay()
        NSLog("Button pressed.")
    }

    ...
Miscellany answered 28/12, 2014 at 17:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.