4

I'm trying to draw to a NSView from a background operation, but don't see any effect.

let queue = OperationQueue()
queue.addOperation() {
  doTheBackgroundStuff()
}

starts the background operation, which is doing lots of calculations. In the AppDelegate I have

@IBOutlet weak var image: NSImageView!  // some image to show
@IBOutlet weak var number: NSTextField! // a corresponding number
@IBOutlet weak var mainView: NSView!    // the main view holding the above

The assignment

number.intValue = Int32(someNumber)

is issued from the background operation regularly (often). But the text does never change. I have set the "can draw concurrently" in IB for the view as well as for the TextField. I also tried

if mainView.lockFocusIfCanDraw() {
  mainView.setNeedsDisplay(mainView.rectPreservedDuringLiveResize)
  mainView.unlockFocus()
}

after the text field assignment. Also to no avail.

qwerty_so
  • 35,448
  • 8
  • 62
  • 86
  • Does execution reach the inside of `if mainView.lockFocusIfCanDraw() { ... }`? – l'L'l Nov 21 '16 at 21:16
  • @l'L'l Yes, it does. – qwerty_so Nov 21 '16 at 22:36
  • You might want to show the code you're using to update the textfield. – l'L'l Nov 21 '16 at 23:20
  • @l'L'l It's all up there. Right above the `lockFocus`. – qwerty_so Nov 22 '16 at 09:24
  • `can draw concurrently` is a bogus option that indicates that *you* have specifically implemented `-drawRect:` to support concurrent drawing. It says *nothing* about what AppKit will do with the view. Please don't set this unless you're actually locking here. – CodaFi Nov 27 '16 at 19:28
  • @CodaFi Thanks for the pointer. Setting this was due to grasping the straw... Now I have `_initWithWindowNumber: error creating graphics ctxt object for ctxt` which seems to be even more unknown. P.S. This was due to the `flushGraphics`. Still no change... – qwerty_so Nov 27 '16 at 20:26
  • Try to set text of textfield on main thread using `self.performSelector(onMainThread: Selector, with: Any?, waitUntilDone: Bool)` method – Rohit Parsana Nov 30 '16 at 09:48
  • @RohitParsana Sounds like that could work. I'll try later and get back. – qwerty_so Nov 30 '16 at 10:28
  • @RohitParsana No luck. I think I simply give up on this. It's not that important for me. – qwerty_so Nov 30 '16 at 14:43
  • @ThomasKilian, ok, go ahead...! – Rohit Parsana Nov 30 '16 at 15:43

2 Answers2

3

Call flushGraphics as described in https://stackoverflow.com/a/19997698/3419541:

I read about NSGraphicsContext Restriction at Thread guide.

Here, I found the following line:

If you do any drawing from a secondary thread, you must flush your drawing calls manually. Cocoa does not automatically update views with content drawn from secondary threads, so you need to call the flushGraphics method of NSGraphicsContext when you finish your drawing. If your application draws content from the main thread only, you do not need to flush your drawing calls.

Community
  • 1
  • 1
PDK
  • 1,476
  • 1
  • 14
  • 25
  • I tried ` NSGraphicsContext(window:window!).flushGraphics()` right in the lock part. No change. – qwerty_so Nov 21 '16 at 22:45
  • That will get you a new graphics context, you need the [one associated with your window](https://developer.apple.com/reference/appkit/nswindow/1419713-graphicscontext). Make sure to retrieve this from the main thread as well, GraphicsContexts are thread-local. – CodaFi Nov 27 '16 at 19:31
  • When I added this to my code I get an `_initWithWindowNumber: error creating graphics ctxt object for ctx` error. – qwerty_so Nov 27 '16 at 20:30
2

Typically these problems go away if you send your view updating code back to the main queue from within your background task:

DispatchQueue.main.async {
    // your view update code
}

If there's not too many place inside your doTheBackgroundStuff you could just sprinkle those in for view updates, i.e. whenever you access your mainView.

Otherwise it helps to re-group things into parts that do non-UI heavy lifting and then push the view updates to Dispatch.main.async at the end.

sas
  • 7,017
  • 4
  • 36
  • 50
  • That sort of works. I can see that the fields now are changing. But it's slow as molasses. Probably the whole windowing is not designed for that kind of mass-updates as I'd need them. – qwerty_so Dec 01 '16 at 11:04
  • I'd recommend coalescing updates into groups that are refreshed in sensible intervals. You could even transition between them smoothly if your updates lend themselves to that. If you let UIView do the animation between states you'll get smoother results than trying to push updates too frequently. – sas Dec 01 '16 at 18:26
  • Yeah, probably that would make it smoother. But that takes me to find a smart way of buffering update (to say 25 per second). Probably a simple timer would do the job. – qwerty_so Dec 01 '16 at 20:42
  • You can use a dispatch source to queue up changes for a certain length of time with only the last one being actually executed. For instances, I wrote a [`Throttle` struct](https://github.com/feinstruktur/FeinstrukturUtils/blob/master/FeinstrukturUtils/Async.swift) a while back to buffer/throttle network requests that you could adopt. See [the tests](https://github.com/feinstruktur/FeinstrukturUtils/blob/master/FeinstrukturUtilsTests/AsyncTests.swift) for how to use it. Happy to have that bounty if it's working though :) – sas Dec 02 '16 at 07:07
  • Although it does not completely solve my issues (which are likely system/OS-inherent), I'll accept this as correct. Pushes you right over 1k ;-) – qwerty_so Dec 02 '16 at 09:32