I have a Cocoa app with a table view and a few other controls. When the app launches and the window is shown, a blue focus ring is drawn around the table view.
How can I get rid of that focus ring? I'd like nothing to have the focus when the window first shows.
The window has initialFirstResponder binding that shows which control will be active when the window becomes active. Change the initialFirstResponder or adjust tableview settings in interface builder to hide the focus ring
The best way I've found of stopping any of the controls from being the first responder when a window is first displayed is in the window controller:
Swift 3:
class YourWindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
// Wait a frame before setting the first responder to be the window itself.
// We can't just set it right now, because if the first responder is set
// to the window now the system just interprets that as meaning that we
// want the default behavior where it automatically selects a view to be
// the first responder.
DispatchQueue.main.async {
window!.makeFirstResponder(nil)
}
}
}
It's messy, and sometimes when the window loads you see the focus ring starting to appear on one of the controls for one frame, but I haven't found a better way yet.
Related
My Cocoa App uses one ViewController. I do not use the InterfaceBuilder On app launch a view will be created and the user can do stuff. When clicking a specific button the VC (as the view's delegate) receives a message and then replaces the view with another.
In this new view I want a specific UI element to be the first responder. So far I have not been successful.
The new view has a reference to the desired element (a subview), so the VC can pass it to the window's makeFirstResponder(:_) method.
I tried to do that in the following places:
at the end of the view's init
in the view controller's viewWillAppear()
in the VCs viewDidAppear()
in the latter two I tried:
if let myView = self.view as? MyView {
... here I try to set the UI element as firstResponder ...
}
But in any case I get the following Message:
[General] ERROR: Setting <NSTableView: 0x7f8c1f840600> as the first responder for window <NSWindow: 0x7f8c1ef0efc0>, but it is in a different window ((null))! This would eventually crash when the view is freed. The first responder will be set to nil.
So it appears that at the time I try to set the firstResponder the new view has not yet been attached to the window.
What I also tried is to override the MyView's becomeFirstResponder()method, assuming that when the view is finally presented in the window it will receive that command, but unfortunately this method does not get called.
Is there an easy way to specify an entry point for the responder chain / key view loop per view?
macOS 10.12.6; Xcode 9.3, storyboards
I have an NSTabView (tabless) that in itself contains two NSTabViews. One is tabless, the other one uses the 'toolbar' style.
When I start my app with the toolbar visible, everything is fine: it displays my tabs in the toolbar, I can change them, etc etc. Once I change to the other branch of my storyboard, the toolbar disappears... and when I come back, instead of a toolbar proper, with buttons and all that, I get a slightly widened bar that has no content in it.
I've set up a sample project to show my problem, where - for ease of switching - I have left the other two tabViewControllers to show their tabs (bottom/top, but this makes no difference).
1) First run (starting with 'toolbar' branch):
2) (not shown): switch to 'top' branch
3) After switching back to 'toolbar':
As a diagnostic aid, I've created a 'displayToolbarStatus' IBAction in the AppController:
#IBAction func displayToolbarStatus(_ sender: NSMenuItem){
if let window = NSApplication.shared.windows.first {
print(window.toolbar?.isVisible)
}
}
The results are as follows:
1) optional(true)
2) nil
3) optional(true)
which is very much in line with how things should work: the toolbar exists and is displayed, there is no toolbar, the toolbar exists and is displayed. Only, of course, it is not usable as a toolbar. (turning visibility off and on, or trying to force a size change with window.toolbar?.sizeMode = .regular has no effect whatsoever, nor does assigning icons to the toolbar items; the toolbar remains squashed and without functioning buttons.
I haven't worked in any depth with NSToolbar: is this a known problem with a workaround; is this new to Xcode 9.2 (which, after all, thinks that no window is valid, so obviously has some problems in that field)?
I really want to use the NSTabView 'toolbar' functionality: how do I proceed?
I've now had more time to play with toolbars. The 'weird' appearance of the non responsive toolbar is simply an empty toolbar, which gave me a clue as to what was going on.
0) The NSTabView overrides the window's toolbar; it does not hand back control when it vanishes; this means that if you have another toolbar in your window that will never show up the moment you're using an NSTabView with 'toolbar' style.
1) I have added a print statement to every relevant method in the ToolbarTabViewController and a 'Switching Tabs' in the containing TabViewController's DidSelect TabViewItem, as well as logging when Toolbar items are added to the window.
(The ToolbarTabViewController is the second controller in the containing TabViewController; it is selected. Otherwise the stack looks slightly different):
ViewDidLoad
Switching tabs
viewWillAppear
viewDidAppear
Switching tabs
Toolbar will add item
Toolbar will add item
viewWillAppear
viewDidAppear
Switching away to the other tab:
viewWillDisappear
Switching tabs
Toolbar did remove item
Toolbar did remove item
viewDidDisappear
So far, so good.
Switching back to the ToolbarTabController, we get
viewWillAppear
Switching tabs
viewDidAppear
Whatever method is called that adds the tabs-related items to the toolbar on first appearance does never get called again. (Note also that the the order of switching tabs and viewDidAppear is not consistent between the first and subsequent appearances.)
2) So, the logical thing to do seems to be to capture the items that are being created and to add them back for future iterations. In the ToolbarTabViewController:
var defaultToolbarItems: [NSToolbarItem] = []
#IBAction func addTabsBack(_ sender: Any){
if let window = NSApplication.shared.windows.first {
if let toolbar = window.toolbar{
for (index, item) in defaultToolbarItems.enumerated() {
toolbar.insertItem(withItemIdentifier: item.itemIdentifier, at: index)
}
}
}
}
override func toolbarWillAddItem(_ notification: Notification) {
// print("Toolbar will add item")
if let toolbarItem = notification.userInfo?["item"] as? NSToolbarItem {
if defaultToolbarItems.count < tabView.numberOfTabViewItems{
defaultToolbarItems.append(toolbarItem)
}
}
}
3) The last question was when (and where) to call addTabsBack() - I found that if I try to call it in viewWillAppear I start out with four toolbarItems, though the number of tabViewItems is 2. (and they do, in fact, seem to be duplications: same name, same functionality). Therefore, I am calling addTabsBack()in the surrounding TabViewController's 'didSelect TabViewItem' method - willSelect is too early; but didSelect gives me exactly the functionality I need.
4) There probably is a more elegant way of capturing the active toolbarItems, but for now, I have a working solution.
I would like to display an informational NSPopover that tracks the user's mouse cursor.
For this, I am using an NSTrackingArea to update the popover's positioningRect whenever the mouseMoved event fires.
However, this has two drawbacks:
The popover follows the mouse with a slight delay. How can I reduce this delay to make the popover appear more "glued" to the mouse pointer?
When I move the mouse pointer in the direction of the popover, the tracking area's mouseExited method gets called, which causes the popover to "absorb" the mouse movement events, so that the tracking area's mouseMoved event no longer fires. How can I avoid the popover absorbing the mouse events, or at least keep forwarding these events?
This question is very similar to Any way around this NSTrackingArea quirk?, with the distinction that I am using NSPopover, so I have nothing to set ignoresMouseEvents on.
I took a look at your problem. I was not able to eliminate the lag, but it might reduce if you set popover.animates to false.
Wrong approach:
I was able to solve the mouseExited over popover issue by adding a new border (and shadowless) window on top of the other one. The trackingArea is added to the transparent window, the popover to the original one. Depending on the transparent windows level, it is above the popover and therefore they can't interfere with each other.
In the gif below you can see the results of my tests:
Here is some of my code:
The mouse tracking:
override func mouseMoved(with event: NSEvent) {
let location = self.view.convert(event.locationInWindow, from: nil)
popover.positioningRect.origin.x = location.x
popover.positioningRect.origin.y = location.y
}
The custom window:
transparentWindow.backgroundColor = NSColor.clear
transparentWindow.isOpaque = false
transparentWindow.styleMask = .borderless
transparentWindow.makeKeyAndOrderFront(nil)
Update 11/11/2016:
I just read the question in the link you provided. There is a window to set ignoresMouseEvents on. Even though NSPopover inherits from NSObject, you have a contentViewController, which holds an view object, which holds the popovers window. (as explained here)
So simply set
popover.contentViewController?.view.window?.ignoresMouseEvents = true
after the popover is shown.
I have noticed when an apps window contains an Outline view (such as XCode) it changes color when that window is in focus. With XCode for example, if the window is current then the outline view has a blueish background, if it looses focus it goes grey,
Can anyone help me to replicate this? I presume its something to do with drawRect: but can only manage to get the color to change when the window loads.
Maybe its a built in function and I'm just missing something?
All you have to do in your -drawRect: is check whether the window has main status and draw accordingly:
- (void)drawRect:(NSRect)rect
{
if ([[self window] isMainWindow]) {
// draw active appearance
} else {
// draw inactive appearance
}
}
A window's delegate gets messages whenever a window gets or resigns main or key window status. You can implement the appropriate methods (like -windowDidBecomeMain: and -windowDidResignMain:) in your window delegate to update the window and its subviews as necessary.
Is it possible to give an NSView inside an NSPanel first responder status without giving the NSPanel key window status (making the main application window resign key)?
Thanks.
Well, I ended up figuring this one out, but it took a lot of research so I'll post the details here in case anyone else runs into the same problem. First of all, a few basics:
It's impossible to have 2 windows actually be key at the same time
It's possible to fake a window into thinking it's key by overriding -isKeyWindow but that won't give the views contained in the window first responder status.
My Scenario:
I added a child window containing an NSTableView into my main application window (the reason is irrelavant). The child window was an NSPanel with NSBorderlessWindowMask. I wanted to give the NSTableView first responder status without making the panel the key window because it took away focus from the main window (and the whole point of the child window illusion was to make the child window look like it was part of the main window).
The first thing I tried was fooling the table view into thinking that it was inside the key window by overriding isKeyWindow to return YES. This made the table view draw as if it were the first responder, but still did not give it first responder status.
The Solution:
So by default, NSBorderlessWindowMask will not allow the window to become key. To make the table view first responder, the window had to be key so I overrode canBecomeKeyWindow in the borderless window subclass to return YES. This, of course, took away key status from the main window, which was one of the things I wanted to avoid. To fix this, I subclassed my main window and overrode the following methods:
- (BOOL)isMainWindow
{
return YES;
}
- (BOOL)isKeyWindow
{
return ([NSApp isActive]) ? YES : [super isKeyWindow];
}
This subclass checks if the application is active, and if it is, it always returns YES so that no matter what window is active in your application, the main window will always behave as if it is still key. This sort of gives the illusion that you can have multiple windows be key at the same time and enables you to shift key window status to another window without losing it on your main window. Hope this helps!