How to make child controls of view will get focus on Tab and Shift+Tab in NSViewController - cocoa

I have to upgrade one existing application and neeed to split its existing UI into separate NIBs. I have planning to start with creating separate NIBs and NSViewController for all my splitted UIs. Now the problem is my NSViewController didn't respond on keyboard TAB and SHFIT+TAB events, I simply wants my NSViewController to set focus on appropriate child control in my dynamically loaded NSViewController view when user click TAB or SHIFT+TAB.
Thanks,
EDITED :
Following is my requirement.
I have three sub views which i need to load dynamically and switch using NSPopupButton in my MainWindow placeholder NSBox.
For checking i have created new cocoa app and added one NSPopupButton and NSBox to Window and join the outlets for NSPopupButton and NSBox.
Second, I have created three new NSViewController's with three different NIB's containing separate custom view containing two or three NSTextField's child controls.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
}
In the main app delegate function i am adding all the three NSViewController's to an array and later swapping of views using replaceSubview to replace views in placeholder NSBox.
I have added following code in all the three NSViewController's, but i am still not getting focus on child controls by pressing TAB or SHIFT+tab keys.
- (void)loadView {
[super loadView];
// store the responder that’s right after the view in the responder chain
NSResponder *nextResponder = [[self view] nextResponder];
// set the view controller (self) as the next responder after the view
[[self view] setNextResponder:self];
// set the stored responder as the next responder after the view controller
[self setNextResponder:nextResponder];
}

Even though NSViewController inherits from NSResponder, Cocoa doesn’t automatically add NSViewController instances to the responder chain. You need to do it yourself.
One possible solution is to insert your view controller into the responder chain between the view it is controlling and the next responder of that view. For instance, in your view controller implementation,
- (void)loadView {
[super loadView];
// store the responder that’s right after the view in the responder chain
NSResponder *nextResponder = [[self view] nextResponder];
// set the view controller (self) as the next responder after the view
[[self view] setNextResponder:self];
// set the stored responder as the next responder after the view controller
[self setNextResponder:nextResponder];
}

Related

OS X storyboards: using "show" segue without allowing duplicate new windows to show?

Right now I have an OS X storyboard app that has a main window, and a button on it that triggers a "show" segue for another view controller. Right now I've got the segue set up as modal because if I don't, the user could click the same button again and end up with two copies of the same window.
Is there a way for me to accomplish this without having to restructure the storyboard to embed these view controllers in a separate window controller (which would seem to defeat the purpose of the flexibility the storyboards offer)?
Edit: While the answer below does work, it is definitely not the best way. In your storyboard, select the view controller for the destination view then go to the attributes inspector and change Presentation from Multiple to Single. That's it, no code required.
Not sure this is the best way but in the NSViewController that is pushing the segue, you could add a property for the destination NSViewController and, in your prepareForSegue:sender: method, assign the destination view controller. Finally, in the shouldPerformSegueWithIdentifier:sender: method, check to see if the destination view controller is assigned, and, if so, bring its window to the front and return NO meaning don't perform the segue, otherwise, return YES. Here's a quick example (to be included in the NSViewController with the button to initiate the segue):
#interface ViewController ()
#property (weak) NSViewController *pushedViewController;
#end
#implementation ViewController
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if (self.pushedViewController) {
[self.pushedViewController.view.window makeKeyAndOrderFront:self];
return NO;
}
return YES;
}
- (void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
self.pushedViewController = segue.destinationController;
}
#end
When you close the window containing the destination view controller, this will set the pushedViewController property of the original view controller to nil so the segue will perform if the window is not already opened. Again, there may be a better way to do this. Hope this helps.
Jon

IBAction not triggering NSView drawing

I'm missing something fundamental about NSView. I have a Cocoa Application with an Objective C class named DataSource that is just a regular class, it's not in the nib. The data source has a single instance variable, an NSColor *, and it has a getter and setter.
The view class instantiates the DataSource in awakeFromNib:
- (void)awakeFromNib{
NSLog(#"awakeFromNib");
ds = [[DataSource alloc] init];
}
and then queries the DataSource for the color to use in drawRect. It works fine. I also implement
- (void)mouseDown:(NSEvent *) anEvent;
in the view class, change the color of the DataSource, and then call
[self setNeedsDisplay:YES];
and it also works as I expect when I click in the custom view.
But if I hook up a button in the nib, wired to this IBAction in the view class:
- (IBAction)buttonPushed:(id) sender {
NSLog(#"buttonPushed");
[ds setData:[NSColor cyanColor]];
[self setNeedsDisplay:YES];
}
the data source updates, but drawRect is never called, despite setNeedsDisplay. In my more complicated version, if I click in the view (in a way that doesn't change the color), I will then get the update (caused by the button). Something is delaying drawing. How can I fix this?
Update: There is no controller and there are no outlets. The NSView subclass contains buttonPushed. The data source updates immediately upon button push, but drawing is delayed, despite calling setNeedsDisplay:YES from the view class. Drawing is delayed indefinitely, unless something else happens to trigger it.
Where is the IBAction located? Are you using some view controller? Is the NSView an outlet in that controller?

How do i set a modal segue (programmatically) to a push segue

i have an navigation controller in my storyboard, but for one reason i have to make one segue programmatically, but how do i make a push segue programmatically?
this is my code so far:
- (IBAction)nextviewButton:(id)sender {
HolesViewController *Holes = [self.storyboard instantiateViewControllerWithIdentifier:#"HolesViewController"];
Holes.nameString = self.NameField.text;
[self presentViewController:Holes animated:YES completion:nil];
}
Alternatively, I actually prefer to define a segue right in my storyboard, but rather than originating from the button, I have it originate from the view controller itself, e.g.:
Then, give that segue a "storyboard id", say "Details", and then you can invoke that segue programmatically via:
[self performSegueWithIdentifier:#"Details" sender: ...]; // you can specify either the button or `self` for the `sender
I like this because that way, the storyboard continues to visually represent the flow of the app (as opposed to possibly having scenes floating out there with no segues). And you can use the exact same construct for both push and modal segues (and the view controller that's presenting the next view controller doesn't care which one the storyboard uses).
[self.navigationController pushViewController:Holes animated:YES];
Got it

NSTextView won't become first responder

Here's the layout of my little test app:
AppDelegate owns WindowController.
WindowController owns CustomTextContainerView.
CustomTextContainerView owns an NSScrollView which embeds MyCustomTextView (an NSTextView subclass).
The xibs for both the standard MainMenu and my window controller are relatively empty. My window controller's -windowDidLoad looks like this:
- (void)windowDidLoad {
[super windowDidLoad];
// create CustomTextContainerView
[[self window] setContentView:self.customTextContainerView];
}
What I'm trying to do is set first responder to the textView, but I've tried everything I can think of to get this to work.
I've made it so CustomTextContainerView just forwards -becomeFirstResponder on to its textView. I've tried calling it directly on both the container and the textView but I can't get it to become first responder automatically.
Note: The user can still click in the text area and start typing, but what I'm trying to do is set first responder status automatically so I don't have to click before I start typing. What am I missing?
To force the first responder for a window, call this:
[[self window] makeFirstResponder:self.customTextContainerView];
(This assumes that everything else necessary for first-responder status is enabled, e.g. the view can't have overridden acceptsFirstResponder to return NO.)

NSViewController and multiple subviews from a Nib

I'm having a difficult time wrapping my head around loading views with Interface Builder and NSViewController.
My goal is to have a view which meets the following description: Top bar at the top (like a toolbar but not exactly) which spans the entire width of the view, and a second "content view" below. This composite view is owned by my NSViewController subclass.
It made sense to use Interface Builder for this. I have created a view nib, and added to it two subviews, laid them out properly (with the top bar and the content view). I've set File's Owner to be MyViewController, and connected outlets and such.
The views I wish to load in (the bar and the content) are also in their own nibs (this might be what's tripping me up) and those nibs have their Custom Class set to the respective NSView subclass where applicable. I'm not sure what to set as their File's Owner (I'm guessing MyController as it should be their owner).
Alas, when I init an instance of MyViewController none of my nibs actually display. I've added it to my Window's contentView properly (I've checked otherwise), and actually, things sort of load. That is, awakeFromNib gets sent to the bar view, but it does not display in the window. I think I've definitely got some wires crossed somewhere. Perhaps someone could lend a hand to relieve some of my frustration?
EDIT some code to show what I'm doing
The controller is loaded when my application finishes launching, from the app delegate:
MyController *controller = [[MyController alloc] initWithNibName:#"MyController" bundle:nil];
[window setContentView:[controller view]];
And then in my initWithNibName I don't do anything but call to super for now.
When breaking out each view into its own nib and using NSViewController, the typical way of handling things is to create an NSViewController subclass for each of your nibs. The File's Owner for each respective nib file would then be set to that NSViewController subclass, and you would hook up the view outlet to your custom view in the nib. Then, in the view controller that controls the main window content view, you instantiate an instance of each NSViewController subclass, then add that controller's view to your window.
A quick bit of code - in this code, I'm calling the main content view controller MainViewController, the controller for the "toolbar" is TopViewController, and the rest of the content is ContentViewController
//MainViewController.h
#interface MainViewController : NSViewController
{
//These would just be custom views included in the main nib file that serve
//as placeholders for where to insert the views coming from other nibs
IBOutlet NSView* topView;
IBOutlet NSView* contentView;
TopViewController* topViewController;
ContentViewController* contentViewController;
}
#end
//MainViewController.m
#implementation MainViewController
//loadView is declared in NSViewController, but awakeFromNib would work also
//this is preferred to doing things in initWithNibName:bundle: because
//views are loaded lazily, so you don't need to go loading the other nibs
//until your own nib has actually been loaded.
- (void)loadView
{
[super loadView];
topViewController = [[TopViewController alloc] initWithNibName:#"TopView" bundle:nil];
[[topViewController view] setFrame:[topView frame]];
[[self view] replaceSubview:topView with:[topViewController view]];
contentViewController = [[ContentViewController alloc] initWithNibName:#"ContentView" bundle:nil];
[[contentViewController view] setFrame:[contentView frame]];
[[self view] replaceSubview:contentView with:[contentViewController view]];
}
#end
Should not MainViewController be a subclass of NSWindowController? And the outlets in the class connected to view elements in the main Window in MainMenu.xib?
Let's hope old threads are still read...

Resources