views:

454

answers:

4

I am writing an Objective-C application which communicates with a USB device. The application writes certain data to the device continuously and displays the status of the write operation in a textView, which is an object of NSTextView. I call the -[NSTextView insertText:] method in the loop when I get the write operation status from the device.

The problem is that the NSTextView doesn't get updated at every call of -insertText:. I get the entire contents of the NSTextView only after the entire loop has been executed.

I didn't see an appropriate refresh or update method for NSTextView class. How can I recieve the status of the operation and update the NSTextView simultaneously?

- (IBAction)notifyContentHasChanged:(NSInteger) block {
    NSString *str;
    str = [NSString stringWithFormat:@"Block Written Successfully: %d\n", block];
    [data insertText:str];
}

- (IBAction)func {
    while(USB_SUCCESS(status))
    {
        printf("\nBlocks Written Successfully: %d",BlockCnt);
        [refToSelf notifyContentHasChanged:BlockCnt];
    }
}

Note that the printf on console is updated timely but not the NSTextView.

+3  A: 

Mac OS X does not flush changes to views to the screen immediately, to avoid the flicker and tearing that's common in such situations. This means you can't just sit in a loop and perform blocking operations while you update a view; if you do this, neither the view nor anything else in your application's human interface will update, and your application will appear hung and eventually get the spinning cursor.

(The spinning cursor appears when an application hasn't communicated with the window server after a small amount of time; there is no way to suppress this, by design. It doesn't mean "wait," it means "this application has gone off into the weeds" and you should strive to ensure it never appears for your users.)

To manage blocking operations in Cocoa, you can do one of two things:

  1. You can perform the blocking operations in a background thread and communicate the results to the main thread for updating your human interface. For example, you can do I/O or socket communications this way using select(2) and the standard UNIX file I/O calls.

  2. You can schedule the operations on a run loop (such as the main run loop) if a compatible version is available; this will typically manage a background thread for the blocking version of the operation for you. For example, NSFileHandle provides a run loop compatible abstraction atop file descriptors; NSStream does this for socket communications as well.

Chris Hanson
A: 

The problem is solved. It required displayIfNeeded() method of the NSView class.

shrads
-[NSView displayIfNeeded] is almost never the right thing to use. It's not really "solved" - your users will still get the spinning cursor, because your application isn't handling events normally. You really should be doing the work in the background.
Chris Hanson
A: 

You should be able to refresh the NSTextView via the -setNeedsDisplay: method.

titaniumdecoy
Generally, you don't tell a view to -setNeedsDisplay: from outside — a view tells itself -setNeedsDisplay: when one of its properties changes. Furthermore, any redisplay will happen the next pass through the run loop, not immediately, which is exactly what the questioner is seeing.
Chris Hanson
I use -setNeedsDisplay: all the time on views from outside those views. Can you provide more information as to why that should be avoided, or do I misunderstand you? Thanks.
titaniumdecoy
A: 

I have a loop that is accessing data off the 'net and stuffing it in a db. As I'm processing, I would like a console-like window (not the one in Xcode since this would be a compiled app.)

So I have a NSTextView that I'm changing the textStorage of and scrolling to the bottom each time through the loop. Unfortunately, the only visible changes occur when the loop is finished (though correctly.)

Is there some kind of simple "OK, update the screen now" method without having to get low level or writing much more code?

Troy Sartain
This does not answer the question. Moreover, it *is* the question, and the correct answer to your question is the correct answer to this question: Don't block your UI. You can force the view to display, but that doesn't help the fact that you have completely blocked your UI—you are preventing the user from canceling by any means except force-quitting your app, and from doing anything else in your app (e.g., fetching two DBs at once). Don't do that. The real solution is to receive the data asynchronously, and copy data to the DB only upon receiving it.
Peter Hosey
OK, I understand not blocking the UI. Are you suggesting using something like an NSOperation? I'm having trouble visualizing how to process an array without using a direct for-loop. Can anyone give a general structure of what the code flow would look like?
Troy Sartain