NSTableView, NSArrayController and reload only after key press? - cocoa

I have the following situation:
There is one custom view inside of the first window that contains a NSTableView.
There is a second window which acts as a form for the current object behind the selection of the table view inside the first window.
Some more details:
I’ve implemented the setDoubleAction: behavior in the NSTableView that basically opens the second window
The table view is bound to the arrangedObjects of an (subclassed) NSArrayController
The specific interface elements in the second window (that opens on double click) are bound to the selection of the NSArrayController
I’ve subclassed the NSArrayController and modified the following functions:
At first I changed addObject: (or add:, this doesn’t really matter):
- (void)addObject:(id)object
{
[super addObject:object];
[self saveTemplatesToDisk];
}
Then I changed remove:
- (void)remove:(id)sender
{
[super remove:sender];
[self saveTemplatesToDisk];
}
The action that opens the preference sheet is just a one liner: [NSApp beginSheet:preferenceWindow modalForWindow:[_preferenceView window] modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
The code that get’s executed after the user presses the return key / OK button isn’t complicated either.
It just saves the current content of the array controller to disk and closes the second window:
- (IBAction)endPreferenceSheet:(id)sender
{
[templateArrayController saveTemplatesToDisk];
[NSApp endSheet:preferenceWindow];
[preferenceWindow orderOut:nil];
}
Finally here’s my problem / question
When I press the return key in the second window, the window closes, the data gets saved and the NSTableView gets properly reloaded without any further interaction. But when I press on the OK button with the mouse, nothing seems to happen. Here’s the interesting part: when I now select another row in the table view in the first window after the second window disappeared, the previously selected row (read: the updated object) gets properly reloaded and displays the content I’ve edited in the second window that has interface elements bound to the selection.
Basically my implementation works, but not when the user uses the mouse to close the window.
The only difference I can spot is the currentEvent, but I can’t imagine how this could change the behavior of this simple application.
When I press the OK button with the mouse: NSEvent: type=LMouseUp loc=(563.055,30.1484) time=58450.2 flags=0 win=0x0 winNum=5371 ctxt=0x0 evNum=8093 click=1 buttonNumber=0 pressure=0 subtype=NSTabletPointEventSubtype deviceID=0 x=19469 y=15838 z=0 buttons=0x0 pressure=0.000000 tilt={0.453108, -0.140629} rotation=0.000000 tangentialPressure=0.000000 vendor1-3=(0, 0, 0)
When I press return: NSEvent: type=KeyDown loc=(0,300) time=58474.8 flags=0 win=0x0 winNum=5371 ctxt=0x0 chars="
" unmodchars="
" repeat=0 keyCode=36
Any ideas how I can solve my problem?

Remember the responder chain: The keyboard event starts at the first responder, which will be the field editor, then (if that doesn't handle it) goes to the next responder, which will be the table view. The mouse event goes directly to the view that the user clicked on, which is the button.
So, the difference is that the table view handles the return event, but it never sees the mouse event. When the user clicks, you simply get an action message from the button—the table view remains in in editing mode.
The solution is to have the action method tell the controller to commit editing before proceeding with the actual action.

Related

How do I set the initial first responder per view in a cocoa app that switches between different views?

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?

NSTableView allow selection change without becoming first responder

I've a window with a split view. Left is a NSTableView, to the right a custom view.
When my custom view is active in a 'command mode' I need it to remain first responder status so it can receive a cancelOperation: event when the escape key is pressed. But I do want the user to be able to change the selection in the table view.
Unfortunately, as long as my custom view refuses to resign first responder status the table view doesn't respond.
How can I make sure that the table view allows changing the selection without becoming first responder? Or how can I make sure the cancelOperation: event is delivered to my custom view, while it's not first responder?
the table view allows changing the selection without becoming first responder
Don't do this. It will confuse the user. The table view should become first responder.
Or how can I make sure the cancelOperation: event is delivered to my custom view, while it's not first responder
Put Cancel button in the window with key equivalent Escape. Or if you don't want a button, let an object in the responder chain (view controller, window controller) catch the escape key by implementing cancel: and tell the custom view to cancel.

Understanding first responder behavior

From the app delegate, I make a window and set it's contentView to be a view programmatically generated from a plist specification. I then bring the window to front. The window has a toolbar, and when the buttons on the toolbar are pressed, it is supposed to display a different contentView.
I have found that the first content view appears with its topmost text field subview already selected as first responder, but changing the view from the toolbar (it sets contentView on the window) to a different view will not select any of that view's text fields as first responder.
I want to have consistency, so ideally either it would never auto-select a control as first responder or it would always auto-select a control as first responder, but I don't really understand what process is making the control first responder in the first place.
Could somebody please explain what is causing that, so I can either prevent it or try to emulate it when switching views?
hussain Shabbir's answer is on the right track, but misses a few things.
First, setting the window's initial first responder, and then making the same view its first responder, is redundant. The point of the first is to cause the second.
Second, you need to set the window's initial first responder before making the window visible:
Sets a given view as the one that’s made first responder (also called the key view) the first time the window is placed onscreen.
If the window is already visible when you set its initial first responder, nothing will happen.
You need to set the initial first responder before you make the window visible for the first time.
The best place to do that is not in code at all—it's in the nib.
You would then not have either of those lines of code.
Better yet:
The window has a toolbar, and when the buttons on the toolbar are pressed, it is supposed to display a different contentView.
Have you considered using NSTabView? It handles this automatically (every tab view item has its own initial first responder outlet).
If you want when you click on different views your textfield should be become first responder, Then the two lines of code below should work:-
Here on the basis of your condition use these below lines:-
[[self window] setInitialFirstResponder:(NSView *)YourTextFieldName];
[[self window] makeFirstResponder:(NSView *)YourTextFieldName];

NSWindowController shows new window

I am very new to mac programming. Just started before 3 days.
I am making a sample app in which i have one button in main window
I am using this code to open a new wndowcontroller
ThirdViewController *tvc = [[ThirdViewController alloc] initWithWindowNibName:#"SecondViewController"];
[tvc showWindow:self];
This working fine but when i press button again it will open same window again so after every click i have +1 window on screen.
What i want is if my new window is already on my screen then button can't add same window.
Thanks in advance:)
If that code is being executed whenever the button is clicked then you’re effectively creating a new window controller, loading its window from a nib file, and showing that window as many times as the button is clicked.
The standard approach to prevent this from happening is having an instance variable that is initially nil and assigning it a window controller only once. Subsequently, the instance variable is not nil any longer and you can test that to avoid creating another controller and loading the nib file again.
You could, for example, declare the following instance variable in your application delegate or whatever controller should be responsible for the third window controller:
ThirdViewController *tvc;
and, when the button is clicked:
if (nil == tvc) {
// If tvc is nil then it's the first time this code is being executed
tvc = [[ThirdViewController alloc] initWithWindowNibName:#"SecondViewController"];
}
[tvc showWindow:self];

Make NSView in NSPanel first responder without key window status

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!

Resources