NSTextView setNeedsDisplay not working under Mavericks? - cocoa

My MacOS Cocoa application displays a window of static text, meaning it should not be changed by the user, should not be first responder, etcetera. The only thing that happens to the text is that each word of it changes color (from "idleColor" to "highlightColor", and then back again) at a specific point in time. It is similar to a Karaoke display - individual words change color, and then change back, under program control, based on a list of timed events.
All of this works beautifully under MacOS 10.7 and 10.8. BUT, under 10.9, the text color does NOT change UNLESS I click in the window and continually move the cursor around, so I am manually highlighting (and un-highlighting) some of the text, continuously. If I do this, the regular words behave as intended. Essentially, it feels like the OS is refusing to update the window under program control, unless I am forcing it to update by manually performing something that requires the UI to respond.
The code that performs the color changes is as follows:
if (sEvent.attribute == HIGHLIGHT_ON) {
[sTextView setTextColor:highlightColor range: currentRange];
textIsLitUp = YES;
}
else {
[sTextView setTextColor:idleColor range: currentRange];
textIsLitUp = NO;
}
[sTextView setNeedsDisplay:YES];
(sTextView is a subclass of NSTextView.)
Now, if I comment out that last line, then I get the same, incorrect behavior under 10.7 and 10.8. In other words, under 10.9, the setNeedsDisplay method is not working, or not working the same way.
Does anyone have any ideas about working around this, or have any other light to shed on the problem? Or am I doing something terribly wrong? It is CRITICAL to the application that the changes to the textColor happen without latency!
EDITING MY QUESTION - to answer it:
Found the answer elsewhere here! I needed to call setNeedsDisplay on the main thread - it was in a secondary thread. The weird thing is that it always seemed to work fine under 10.7 and 10.8. It only broke under 10.9. So I just changed this:
[myTextField setNeedsDisplay:YES];
To this:
dispatch_async(dispatch_get_main_queue(), ^{[myTextField setNeedsDisplay:YES];});
…and it seem to have worked. Hope this helps someone else…

You don’t want to do any of the changing of AppKit objects in non-main threads—it’ll work sometimes, maybe even often, but then every once in a while it’ll crash, and you’ll wonder why. So:
[sTextView setTextColor:idleColor range: currentRange];
needs to be on the main thread, too.

Related

Updating contents of multiple NSTextView objects in a single operation

The database application I am working on can have a window with multiple NSTextView elements for displaying and editing data. When the current spot in the database is repositioned, all of the NSTextView objects in the window need to be updated with new contents. This is done with a loop that scans each object and checks to see if it needs to be updated. If it does, the new value is calculated, then updated by using the [NSTextView setString:] method. Here is a simplified version of the code involved.
for formObject in formObjectsInWindow {
NSTextView * objectTextView = [formObject textView];
NSString * updatedValue = [formObject calculateValue];
[objectTextView setString: updatedValue];
}
This works, but if there are a lot of objects, it is somewhat slow. Probably related, the display does not update all at once, you can actually see a "ripple" as the objects are updated, as illustrated in this movie (this movie has been slowed down to 1/4 speed to make the ripple effect more pronounced, but it is definitely visible at full speed).
If you've gotten this far, you might suspect that the calculateValue method is slow, but that isn't the problem. In other places the same code is used and runs at tens of thousands of operations per second. Also, this delay only occurs during update operations, it doesn't occur when the window is first opened, even though the same calculations are required at that time. Here is an example. Notice that when I switch back to the detail view all the NSTextView objects update instantaneously, even though the record changed and all of the values are different.
My suspicion is that the [NSTextView setString:] method is updating the off-screen buffer, then immediately copying that to the on-screen buffer, so that this double buffering is happening over and over again for each item, causing the delay and ripple. If so, I'm sure there must be some way to prevent this so that the screen is only updated at the end after all of the values have been updated. It's probably something simple that I am missing, but I'm afraid I am stumped as to how this is supposed to be done.
By the way, this application does not use layer-backed views, and is not linked against the QuartzCore framework.
I brought up this question with Apple engineers at the WWDC 2018 labs. It turns out the problem is that the setString: method does not mark the NSTextView object as needing display. The system does eventually notice that the text has changed and updates the display, but this happens in an asynchronous process, hence the "ripple" effect. So the workaround is simply to add a call to setNeedsDisplay after calling setString.
[objectTextView setString: updatedValue]
[objectTextView setNeedsDisplay:YES];
Adding this one line of code fixed the problem, no more ripple effect.
I'm told that this is actually a bug, so hopefully this extra line won't be needed in future versions of macOS. As requested, a radar has been filed (41273611 if any Apple engineers are reading this).

VoiceOver announcing text change OS X

I have a non-editable text view (I can make it a text field, it doesn't matter). I change it programmatically when a user presses a button. I want VoiceOver to announce the change without moving the cursor. So the VO cursor stays on the textView/Field and when the text changes, just announce the change.
I'm trying to get this to work with a braille display.
I've tried posting an NSAccessibilityValueChangedNotification but so far I get absolutely nothing.
I don't know if it's the same in OS X, but in iOS land, you might accomplish this by posting a UIAccessibilityAnnouncementNotification.
In the OS X 10.9 SDK, it appears that there's a similar notification available in Lion and beyond called NSAccessibilityAnnouncementRequestedNotification. It takes a userInfo dictionary instead of the simple NSString that UIAccessibilityAnnouncementNotification takes, but it should do what you're asking.
Good Luck!

How do I detect "invalid drawable" in Mac OS X?

I am working on a cross platform application that is over a decade old. The UI is by Qt and backend rendering is done with OpenGL. OpenGL contexts are managed in the back end, not by Qt.
I have recently added error checking and reporting for all OpenGL code in our app. Occasionally a situation arises where the first render initiated by Qt causes an "invalid drawable" error message in the terminal and all subsequent OpenGl calls fail with an "invalid framebuffer" error reported. These invalid drawable error messages have been treated as innocuous in the past, since before the user sees it the drawable eventually becomes valid and the scene is rendered correctly. However, with the new OpenGL error check/report it's not possible since there are large numbers of errors reported.
I would like to test if the drawable is valid. If it is not, it should return before the render starts. How can I verify that the drawable is valid?
MacBook Pro, OS X Mountain Lion (10.8.3), ati graphics card
I don't know at what API level you're working. I'm not sure it's possible to detect the problem after the fact. That is, if all you have is a context (perhaps implicit as the thread's current context) that failed to connect to its drawable.
I presume that Qt is using Cocoa under the hood. I further assume it has created an NSOpenGLContext and is invoking -setView: on it. You get that "invalid drawable" error if, at the time of that call, the view's window doesn't have a window device.
One common technique is to defer setting the context's view until the view has -drawRect: called on it, since at that point you're sure that the view has a window and the window has a device. (Although that ignores the possibility of forced drawing outside of the normal window display mechanism. For example, -cacheDisplayInRect:toBitmapImageRep:.)
If you just want to know at the point of the call to -setView: whether it's safe or not, I think you can rely on checking the value of [[view window] windowNumber]. The docs for -windowNumber say:
If the window doesn’t have a window device, the value returned will be equal to or less than 0.
Another approach is to prevent the problem rather than detect it. The strategy for that is basically to make sure the window has been shown and drawn before the call to -setView:. You may be able to force that by ordering it on-screen and invoking -display on it.
Ken Thomases post gave me the basic info I needed to find a workable solution. An additional conditional was needed. Here's what worked
//----------------------------------------------------------------------------
bool vtkCocoaRenderWindow::IsDrawable()
{
// you must initialize it first
// else it always evaluates false
this->Initialize();
// first check that window is valid
NSView *theView = (NSView*)this->GetWindowId();
bool win =[[theView window] windowNumber]>0;
// then check that the drawable is valid
NSOpenGLContext *context = (NSOpenGLContext *)this->GetContextId();
bool ok = [context view] != nil;
return win && ok;
}

Why does setting initialFirstResponder have no effect?

I have a simple form (NSWindow) with 3 text fields. NSWindow's initialFirstResponder is 'pointing' to the first field (NSTextField). All three text fields are circularly linked to each other via nextKeyView.
Problem that I have is that when I start the application from Xcode it'll focus on the text field that was last active (in focus) when the application closed.
So for example, if I name text fields A, B and C and initialFirstResponder is set to A. Now if I start the application, focus on B, and close the application, next time I start it, the focus will be on B.
Why is that and how would I fix this?
(Sorry if this is a trivial question, these are my first steps in cocoa...)
EDIT:
This is on OS X Lion 10.7.1, Xcode 4.1.
EDIT 2:
I found a way to "fix" this... In the main window (or any window for that matter) XIB/NIB file, click on "Attributes Inspector", then uncheck "Restorable" box. Now the application will not store the last position and so the initialFirstResponder seeing will be respected and followed accordingly.
Welcome to Cocoa! :) I suspect this is happening as part of the new user interface preservation features in OS X Lion. (In fact, I just created a simple app with 3 text fields, and I see this behavior too.) Because windows automatically restore themselves, you will see a lot of this behavior happening automatically even if you didn't implement it. This is probably desirable — most applications will work this way, and the user will come to expect it.
However, if you really want to disable it, you can probably do so by subclassing NSWindow or perhaps NSTextField and overriding -encodeRestorableStateWithCoder:. But, I definitely recommend you leave the default behavior alone.
Edit with a little further information: the app state seems to be stored in ~/Library/Saved Application State/com.yourapp.savedState. There you can see a plist file with information about the windows. The other files don't seem easily readable, but they probably contain information about which field is first responder, etc.
Despite this thread being almost 10 years old I'll gonna add an answer. Just about one month after the answer from jbandes OS X 10.7 Lion was introduced.
Following a quote from NSWindowRestoration.h
#interface NSWindow (NSUserInterfaceRestoration)
/* Determines whether the window should be restored on relaunch. By default, windows with NSTitledWindowMask set in the styleMask are restorable, and windows without it set are not.
*/
#property (getter=isRestorable) BOOL restorable API_AVAILABLE(macos(10.7));

RBSplitView has delayed reload of autosaved view positions

I really enjoy using RBSplitView, an open source replacement for NSSplitView, but I have a problem in my shipping app and am experiencing it again in a new project.
The problem is I'm telling the RBSplitView to autosave its position state by giving it an autosave name. When my app launches the RBSplitView doesn't seem to honor the saved state till a second after the window is drawn.
I've spent the night trying to debug the behavior but have had little success. Anyone out there use this lib and have some advice?
You can scrub this quicktime movie to the issue at work:
http://media.clickablebliss.com/billable/interface_experiments/rbsplitview_delayed_autosave_reload2.mov
I've still been unable to figure out why this is happening but I do have a workaround.
First, make sure your main window is not visible at launch and then at the end of applicationDidFinishLaunching in your app delegate add something like:
[mainWindow performSelector:#selector(makeKeyAndOrderFront:) withObject:self afterDelay: 0.1];
The delay is the key. If you just tell the window to makeKeyAndOrderFront: I still see the issue. However as long as it has a beat of time it looks good.
This likely is happening because the RBSplitView instance needs to wait until it's first moment to get to set its frame to the autosaved value, which happens to be after the user can see it. This 0.0-delay trick simply delays showing the window until the very next runloop, which gives the split view a chance to do its magic (and other views) so that when the user sees the window, it's already nice and sexy. So just do the delay at 0.0 and you'll be fine.
I have a similar, but slightly different workaround in my app that uses RBSplitView. In applicationDidFinishLaunching:, I call adjustSubviews on the split view before calling makeKeyAndOrderFront: on the window that contains it. This seems to knock the split view in to order before it gets displayed on the screen.

Resources