Update NSView while Drawing

Hi there,

My task sounds simple: the goal is to call from the "draw" method of a custom class which extend an NSView another method who do calculation and then draws something in the view.


override init(frame frameRect: NSRect) {
// .. some code here
self.drawMethod(rect: rect)
//
}


here is the method used to draw my stuff:


@objc func drawMethod(rect : CGRect) {
     let width = Double(rect.size.width)
     let height = Double(rect.size.height)
     
     // ... some code here
     for x in stride(from: 0, to: width, by: blockiness) {
          for y in stride(from: 0, to: height, by: blockiness) {
               //
          }
     }

     self.setNeedsDisplay(rect)
}


So far so good: the problem is that the content of the view is displayed after some secods while I would like to display an update each end of the inner for cicle.


Coming from the .NET world, I would do just an "

Application.DoEvents()"


How can I achieve here the same result?


Forgot ... I'm using (learning) Swift 5 and the target is macOS current version :-)

Replies

Did you try adding a setNeedsDisplay at the end of inner loop ?

Hi Claude,

thanks for your hints.


Yes, I did it but without changing the result: the View is rendere all in one at the end of the execution of the method.


Something else I tried was to execute the method in a separate thread, something like this:


DispatchQueue.main.async {
     // some code here
     self.drawMandelbrot(rect: rect)
}


But the first problem is that then the NSGraphicsContext.current is nul and if even I manag to pass as a parameter the context, again same result :-(

All you need to do is override the "draw" method from NSView. Don't call draw from init. Don't call setNeedsDisplay from draw. draw is all you need.

Hi John,

thanks for your reply.


I have tried this approach but I have the same situation.


Now the init do not contains any drawing stuff.


I have the override of the "draw" (the "native" draw method of the NSView) in which I call my code:


public override func draw(_ rect : CGRect) {
// "configuration stuff here"
if (self.needsToRefresh) {
     self.drawMyStuff(rect: rect)
}


then I have


@objc func drawMyStuff(rect : CGRect) {
     let width = Double(rect.size.width)  
     let height = Double(rect.size.height)

     for x in stride(from: 0, to: width, by: blockiness) {
          for y in stride(from: 0, to: height, by: blockiness) {
               NSBezierPath.fill(CGRect(x: x, y: y, width: blockiness, height: blockiness))
          }
     }
     self.setNeedsDisplay(rect)
}


The result is like before, instead of seeing a rectagle filled step by step, I see after some seconds the rectangle completely filled and it looks to me that this is a kind of normal behavior because the drawig is executed in the same thread of the caller :-/

Get rid of that "self.needsToRefresh". If "draw" is being called, then you need to draw - end of story.


Get rid of that "setNeedsDisplay". You would never want to call that from "draw". If it worked at all, it would just lock up your app as it is a recursive loop.


All that being said, it does seem to be working. Drawing is a one-shot deal. You aren't going to see those rects drawn one-by-one. To see each one draw, you might do something like a single call to NSBezierPath.fill in the draw method, using the current "blockiness" value. That's all you need for drawing. To do your quasi-animation, you would have a loop can call setNeedsDisplay repeatedly, while changing your blockiness value. Again, that will all happen faster than you can see, so you'll need a call to "sleep()". And since you never want to sleep on the main thread, you need to call this from a background thread. And since you can't update the UI from a background thread, you need to update "blockiness" and then call setNeedsDisplay via a dispatch_async onto the main thread.