Hide child controls in an NSView - cocoa

I have an NSView with multiple child controls in it. I know I can call [childControl setHidden:TRUE] but I was wondering if its possible to block the message "drawRect:" for the child controls.
Ive noticed that not calling [super drawRect:NSZeroRect] on the NSView does not affect the child controls. So my question is who calls the child controls drawRect message? And if there is a way to block it.
Thanks, Jose.

Every time the controls should react optically, they draw its view again.
If you really want to solve this problem like that, you can create for each control a subclass and add a code like this:
-(void)drawRect:(NSRect)rect {
if (!self.blocked) {
[super drawRect:rect];
}
}
The property "blocked" is a boolean which you have to set to YES or NO if you want to block it.
Note: To totally hide it the control subclass has to be blocked before it draws it self the first time.

Related

NSSplitView Collapse Event

I'm trying to implement an NSSplitView similar to Xcode where you can collapse a view by dragging its handle to under half its width and it will collapse automatically. I have that part working, but I need to update the state of a button in the toolbar when this happens. I tried listening to splitViewDidResizeSubviews and checking if the splitView's view is collapsed, but that method fires 16 times with collapsed == true, so I don't think I want to update the button's state 16 times. Is there a cleaner way to do this? I'm new to Cocoa, but from what I've seen, I would expect there to be some way to just say bind this button's state to the isCollapsed property and be done with it. Does such a thing exist?
If you subclass your NSSplitViewController you can add a listener for the SplitViewItem's isCollapsed property:
class MySplitViewController: NSSplitViewController {
var observer: NSKeyValueObservation?
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
let sideViewSplitViewItem = splitViewItems[0]
observer = sideViewSplitViewItem.observe(\.isCollapsed, options: [.initial, .new]) {splitViewItem, _ in
print("Sidebar collapsed state changed to: \(splitViewItem.isCollapsed)")
}
}
}
The best way to see what bindings are available is to check the docs, specifically the Cocoa Bindings Reference (look in the sidebar for the view you're after).
NSSplitView doesn't have the binding you describe, so I think you're on the right track with your current approach. Of course, you don't need to update the button's state sixteen times, just check it's value each time, and update it if needs be. It might seem a bit wasteful, but checking the value of a bool is a very cheap operation, and you won't notice any kind of performance hit.
While NSSplitView has no event or observable property for when one of it's subviews is "collapsed", the immediate subview itself will have its hidden property set to YES.
So you can either observe the subview's hidden property yourself, or if you're creating your own custom subview of NSView, you can override the -(void) setHidden:(BOOL)hidden to catch the event.
I do the latter, and it works correctly every time.
-(void)setHidden:(BOOL)hidden
{
[super setHidden:hidden];
// Insert code to react to the "collapsed" event here.
// (You're likely going to tell your parent view,
// self.window.windowController, or something.)
}
Of course the setHidden method / hidden property can in theory be called for purposes other than from NSSplitView "collapsing" your view, but this is very unlikely to ever happen in practice.

When WebView is added to a specific view in NSWindow, :hover stops working. How can I fix this?

If I create a WebView and add it to NSThemeFrame like so:
Let window be the NSWindow instance I'm adding to:
NSView * themeFrame = window.contentView;
[themeFrame.superview addSubview:myWebView];
Then any :hover tags of elements in the page I load doesn't work anymore. However, the :hover does appear on mouseDown/mouseUp. So maybe this is a refresh issue. I tried calling [myWebView setNeedsDisplay:YES] in mouseMoved: but no luck.
Is there something special about contentView? I tried the same thing with an NSView and overrode its mouseMoved: method while calling setNeedsDisplay and the NSView handled mouseMove fine. Is there something special about :hover?
Any ideas or ramblings are welcome!
I might have the same problem as Cocoa WebView on os X not firing mouse hover events without having to click and hold left mouse key but there's no answer there.
I had a similar problem and after debugging and reading the source of WebKit, I found a solution. The crucial part seems to the WebHTMLView class, specifically the method -[WebHTMLView _updateMouseoverWithEvent:]. There's a check whether the view's window is a key window and sure enough, my window wasn't key even though I called makeKeyWindow on it.
My window was a child window of a document window and to solve the hover issue, I created a NSWindow subclass and overrode isKeyWindow like this:
- (BOOL)isKeyWindow
{
return [super isKeyWindow] || [[self parentWindow] isKeyWindow];
}
This made hover work in WebView in my document's child window.

Cocoa ignore clicks but still register them?

Is it possible to have a click-through window in cocoa (as in _window.ignoreMouseEvents = TRUE), but still find out when the mouse has been clicked above the window? Or, instead of ignoring the events, registering them, and then somehow forwarding them, propagating them to whatever is behind the window?
I guess what you are looking for is the NSView method hitTest.
This method is called on every view when a mouse click is received.
- (NSView*)hitTest:(NSPoint)aPoint
{
return self;
}
Returning self would mean that the click is not forwarded to the next subviews.
Returning [super hitTest] would just forward the click to the parent view of your view hierarchy. Using this, you could simply register clicks without anything else happening.
So something like:
- (NSView*)hitTest:(NSPoint)aPoint
{
[self propagateEventToNextApplication];
return [super hitTest:aPoint];
}
Hope this helps!

NSWindow tracking

I would like to track each time a certain window appears (becomes visible to the user) in a OS X app. Where would be the most adequate place to call the tracker?
windowWillLoad, maybe?
I expected to find something like windowWillAppear but it seems I'm thinking too much iOS.
How about getting notification such as NSWindowDidBecomeMainNotification, By main I guess the one which is top most on screen directly visible by user.
see : Apple Documentation
Yes, one would expect that a window would notify its delegate or its controller with a windowWillAppear or windowDidAppear message, or post a documented notification like NSWindowDidAppearNotification. But alas, none of those exist. I filed a bug report with Apple and was given the advice to use a storyboard and a view controller instead. This is unhelpful in legacy apps that already use a bunch of window controllers and xibs.
You could subclass NSWindow and override orderWindow:relativeTo: to send a notification. Most, but not quite all, of the messages that make a window show itself ultimately go through this method, including orderBack:, orderFront:, makeKeyAndOrderFront:, and -[NSWindowController showWindow:]. But orderFrontRegardless does not go through orderWindow:relativeTo:, so you would also want to override that for completeness.
Another way to be notified is to make a subclass of NSViewController that controls some view that's always visible in the window. The view controller will receive viewWillAppear and viewDidAppear.
If you're subclassing NSWindow or NSViewController already for some other reason, either of these is a reasonable solution.
If you're not subclassing NSWindow already, and don't have an NSViewController subclass for a view that's always visible in the window, then another way is to use Cocoa bindings to connect the window's visible binding to a property one of your objects. For example, I have a custom NSWindowController subclass. I gave it a windowIsVisible property:
#interface MyWindowController ()
#property (nonatomic) BOOL windowIsVisible;
#end
and I implemented the accessors like this:
- (BOOL)windowIsVisible { return self.window.visible; }
- (void)setWindowIsVisible:(BOOL)windowIsVisible {
NSLog(#"window %# became %s", self.window, windowIsVisible ? "visible" : "hidden");
}
and in awakeFromNib, I bind the window's visible binding to the property like this:
- (void)awakeFromNib {
[super awakeFromNib];
[self.window bind:NSVisibleBinding toObject:self withKeyPath:NSStringFromSelector(#selector(windowIsVisible)) options:nil];
}
When the window becomes visible, the setWindowIsVisible: setter is called with an argument of YES. Note that if the whole app is hidden and reappears, the setter is called again, even though it wasn't called with argument NO when the app was hidden. So be careful not to assume the window was previously hidden.
Also, the binding might create a retain cycle, so you should probably unbind it when the window is closed, unless you want to keep the window and controller around. Note that the window does post NSWindowWillCloseNotification when it's closing, so you don't need any special magic to detect that.

Mouse Events Bleeding Through NSView

I have an NSView which covers its parent window's content view. This view has a click event handler which removes it from the content view. Inside this view, I have another view. When I drag the mouse in this inner view, the mouse events are applied not only to the view in the front, but also to the views behind. Additionally, the cursors from the views behind are showing up as well. This is the same problem occurring here: NSView overlay passes mouse events to underlying subviews? but the answer there won't work for my project because I can't open another window.
Without seeing your event-handling code it's difficult to know what's happening, but I suspect you might be calling super's implementation of the various event-handling methods in your implementations.
NSView is a subclass of NSResponder, so by default un-handled events are passed up the responder chain. The superview of a view is the next object in the responder chain, so if you call, for example, [super mouseDown:event] in your implementation of ‑mouseDown:, the event will be passed to the superview.
The fix is to ensure you don't call super's implementation in your event handlers.
This is incorrect:
- (void)mouseDown:(NSEvent*)anEvent
{
//do something
[super mouseDown:event];
}
This is correct:
- (void)mouseDown:(NSEvent*)anEvent
{
//do something
}
Rob's answer and Maz's comment on that answer solve this issue, but just to make it absolutely explicit. In order to prevent a NSView from bleeding it's mouse events to the parent, one must implement the empty methods.
// NSResponder =========================================
- (void) mouseDown:(NSEvent*)event {}
- (void) mouseDragged:(NSEvent*)event {}
- (void) mouseUp:(NSEvent*)event {}

Resources