-[NSButton setImage:] only redraws the first time it's called - macos

I'm working with a view-based NSTableView that uses a custom cell view with several controls in it. One of the controls is an image-only NSButton that either shows a checkmark image or no image at all. Additionally, when I mouse over one of these buttons that has no image, I would like a "faded" version of the checkmark image to be displayed while the mouse is inside the button.
I've gotten the code set up using NSTrackingArea to respond to -mouseEntered: and -mouseExited: events, calling -setImage: on the NSButton to show the faded checkmark while the mouse is inside the button, then set the image back to nil when the mouse exits.
As far as I can tell, all that code seems to be working as expected, but while the button will update and show the checkmark image the first time the mouse enters the view, then disappear when it exits, moving the mouse back over the button a second time does not cause the button to redraw for some reason, leaving it always blank after the first redraw no matter how many times the image gets set.
This is a summary of the relevant code:
- (void)refreshActiveButton
{
self.activeButton.image = self.activeImage;
}
- (void)mouseEntered:(NSEvent *)theEvent
{
NSLog(#"mouse entered %#", self.listItem.name);
self.mouseOverActiveButton = YES;
[self refreshActiveButton];
}
- (void)mouseExited:(NSEvent *)theEvent
{
NSLog(#"mouse exited %#", self.listItem.name);
self.mouseOverActiveButton = NO;
[self refreshActiveButton];
}
- (NSImage*)activeImage
{
NSLog(#"returning new active image");
if (self.listItem.isActive)
return [NSImage imageNamed:#"checkmark16"];
else if (self.mouseOverActiveButton)
return fadedCheckmark;
else
return nil;
}
The button is set up in a .xib file as a "Momentary Push In" (I've also tried "Momentary Change" - same behavior) with no title displayed and no border.
Running in the debugger, I've been able to confirm that:
The -mouseEntered: and -mouseExited: methods do continue to get called when moving the mouse over the button, even when the display doesn't update.
The correct image is being assigned to the button, and is returned back from the button instance after being set. (either the fadedCheckmark image when the mouse is inside, or nil when the mouse is outside)
I tried calling -setNeedsDisplay:YES on the button after assigning the image, as well as setting a layerContentRedrawPolicy of NSViewLayerContentsRedrawOnSetNeedsDisplay (the entire table view is layer-backed) with no change in behavior. Edit: also tried disabling layer backing altogether, with no change.
I feel like there must be something obvious staring me in the face that I'm missing, but if anyone can clue me in, I'd appreciate it.

The short answer: it's a bug! I've filed it with Apple as radar 20774731, and on Open Radar at http://openradar.appspot.com/radar?id=4957762287042560
The basic sequence is, if you set the button's image to something, then set it back to nil, all subsequent setImage: calls have no effect on the button, forever as far as I can tell. I did forget to mention in the original post that this is all on 10.10.3. I did find some interesting stuff when debugging though.
It appears that once you set an image on the NSButton, after an additional pass of the run loop, it gains an NSImageView as a subview, which is presumably doing the actual drawing of the image. Apple has said they are moving NSCell towards deprecation, so I guess this is the first step, with relieving NSButtonCell of the responsibility of actually drawing the image.
So, the first time you set the image, the image gets set correctly on both the NSButton and the NSImageView. When the image gets set back to nil, they both get their images set back to nil as well. However, when you set the image a second time, while the NSButton's own image property does return back the new image, the underlying NSImageView's image property remains nil. So that might explain why it's not drawing anything.
Ultimately what I ended up doing was using a plain NSImageView with a transparent NSButton right on top of it. I don't get the highlighting you get when you click on a button, but I can live with that.

Related

Core Animation action transition: how do it right

I make a test project with one view inside of content view of main window. Window have two buttons: appear & disappear. I want animate view appearance when appear button pressed, and animate dissapearance when dissapear button pressed. And I want do this with layer actions.
In other words, I want to make animation every time view added or removed from superview. So, in code I just want to write [parent addSubview:view] or [view removeFromSuperview] and animation should work in that moments. So, Layer Actions seems fits my needs.
Here is app screenshot and source code (XCode 4.6):
Here is what I did to do that:
I create a view that will appear/dissapear and add layer action:
_coloredView = [[ColoredView alloc] initWithFrame:NSMakeRect(0, 0, 200, 200)];
_coloredView.bgColor = [NSColor yellowColor];
_coloredView.wantsLayer = YES;
// making appear transition from left
CATransition *appearTransition = [CATransition animation];
[appearTransition setDuration:2];
[appearTransition setType:kCATransitionPush];
[appearTransition setSubtype:kCATransitionFromLeft];
[_coloredView.layer addAnimation:appearTransition forKey:kCAOnOrderIn];
Then when appear button clicked, I call
[contentView addSubview:self.coloredView];
When disappear clicked, I call:
[self.coloredView removeFromSuperview];
And seems some part of things works. The yellow rectangle appears as it should first time. But, here is list of very strange things:
I should call addAnimation:forKey: every time when button clicked (after view was disappeared), since after first appearing animation will no longer work.
Disappear won't work at all (If I try add code after view initialization or before disappear button pressed)
When I try to add subview through animator, my transition not work - just default fade-in animation shown.
Seems that during movement from left to right some sort of fading works. It's visible when you set duration to bigger value.
Try #"subviews" instead of kCAOnOrderIn

NSButtons leave artefacts when used as subviews of a custom toolbar view

I'm placing a few buttons in a simple rectangular NSview which acts as a custom toolbar. On first render the buttons/views come out as expected, but every time a button is pressed (and sometimes with no mouse interaction at all) artefacts start appearing.
Before
After
I can eliminate the artefacts by calling a [self.toolbarView setNeedsDisplay:YES] in all the action and focus methods but this seems like a hack, is there any clean way to deal with this?
It was a beginner's problem. In the drawRect method
- (void)drawRect:(NSRect)dirtyRect
I was using the param dirtyRect for drawing an outline of my view, assuming it was the view's bounds, where in fact it was only the area around the buttons that became dirty when they were pressed. The 'artefacts' were actually my outline being drawn in the wrong place.
By correctly using the bounds of the view
NSRect drawingRect = [self bounds];
the 'artefacts' no longer appeared.
You just try to set focus ring for a buttons to 'none' in IB.

Vanishing NSTextField Text

I have a custom view in a .xib file, which I use as the contentViewController for an MAAttachedWindow. The view has several NSTextFields in it.
When I open the MAAttachedWindow first time, everything is fine. Text shows up in all relevant text fields. Then, if I close the window (which sets it to nil) and then call it again (which reinitializes, using the same custom view as the contentViewController), the last firstResponder text field is now blank.
The strange thing is that if I click the "empty" text field, it shows the correct text. This can be edited, and behaves appropriately as long as this text field has focus. As soon as something else becomes firstResponder, the text vanishes again.
Updates:
Changing the color did not change the aforementioned behavior.
The text color does not change at any time during this process.
Placeholder text also is subject to the aforementioned behavior.
No errors are occurring at any time during this process.
This does not happen to NSSecureTextFields.
I first encountered this problem about 5 years ago with accessory view of a NSSavePanel.
The solution that I've found was to move the first responder to the panel itself, before it's closed. Here's my exact method:
- (void)windowDidEndSheet:(NSNotification *)notification
NSSavePanel *savePanel = [(XSDocument *)[self document] savePanel];
if (!savePanel)
return;
// this fixes a bug where on next opening one of accessory view's text field will be blank and behave strangely
[savePanel makeFirstResponder:savePanel];
}
Try changing color of textfield text to red color (or any other color) you may get what happens here.
I got it!
I simply needed to explicitly remove the viewController from its superview before closing (and subsequently deallocating) the MAAttachedWindow.
Try resigning all first responders before setting the window to nil.

NSTextView overlay causes oddities with first responder status

I have an NSTextView in an NSScrollView, and I am programmatically inserting an NSView subclass as a subview of the NSTextView. This NSView acts as an overlay, superimposing graphical information about the text beneath it.
I thought it was working quite well until I noticed that the text view does not respond to right clicks. Other operations (editing, selection) seem to work just fine.
Also, if the first responder is changed to a sibling of the scroll view (an outline view, for example) the text view does not regain first responder status from clicking on it. The selection will change in response to clicking, but the selection highlight is gray instead of blue (indicating that the text view is not the first responder).
If I offset the frame of the overlay subview, the text view behaves 100% normally in the area not overlapped by the overlay, but the overlapped area behaves incorrectly, as outlined above.
Steps To Replicate This Behavior on Mac OS X 10.6.4:
Create a plain old non-document-based Cocoa app.
Add an `NSTextView' IBOutlet to the app delegate .h.
Add an NSTextView to the window in MainMenu.xib. Connect the textView outlet.
Type in a bit of code:
In applicationDidFinishLaunching:
NSView *overlay = [[NSView alloc] initWithFrame:textView.bounds];
[textView addSubview:overlay];
[overlay release];
Run the app, observe that right click in the text area does not work as it should, yet you can still otherwise interact with the text view.
Next, add an NSOutlineView to the window in the xib. Observe that once focus leaves the text area (if you click on the outline view) with the overlay in place, you cannot set the focus back to the text view (it will not become first responder again).
Is there some way I can enable the NSTextView to receive all of its events, even though my NSView overlay does not accept first responder or mouse events? I suspect this might be related to the field editor – perhaps it is ignoring events it thinks are destined to the overlay view?
You probably need to make your overlay an instance of a custom view class that forwards all events and accessibility messages to the text view. You may also need to convert any view-relative coordinates to the text view's coordinate system.
I don't have a lot of experience with it, but another possibility would be to use a Core Animation layer as an overlay.
A clean way to handle this is by making your overlay view a custom subclass of NSView, and then overriding the hitTest: method to always return nil. This will prevent the overlay view from participating in the responder chain. Instead, events will get sent automatically to it's superview or views higher up the view hierarchy. You might also want to override acceptsFirstResponder to return NO to be safe (in case it's accidentally set programatically).

NSPopUpButtonCell inside custom cell does not pop up when clicked

I've created a custom subclass of NSCell with an NSImageCell, some NSTextFieldCells, and an NSPopUpButtonCell.
I'm initializing the pop up cell using:
myPopUpCell = [[NSPopUpButtonCell alloc] init];
[myPopUpCell setBordered:NO];
[myPopUpCell setAutoenablesItems:NO];
[myPopUpCell addItemsWithTitles:[NSArray arrayWithObjects:#"Item1", #"Item2", #"Item3"]];
And drawing it in drawInteriorWithFrame:inView:
Everything seems to work great, except that when clicking on the pop up cell while running my app the cell does not pop up. Any suggestions about what might be wrong?
Drawing the pop-up button cell in drawInteriorWithFrame:inView: is going to do just that; draw it, but nothing else. Handling click events is unrelated to drawing, so you're going to have to do some work in your custom cell to interpret mouse events, and if they're inside the frame you're using for the pop-up button, pass them on to the button cell. Start by subclassing the methods listed in the NSCell docs under tracking the mouse, such as –trackMouse:inRect:ofView:untilMouseUp:, and you should be be able to figure out what's needed to make the button cell act correctly.
Depending on what you're doing you may actually find it easier to draw the title string yourself, and just use NSMenu's +popUpContextMenu:withEvent:forView:.

Resources