NSViewController mouseDown: not called - cocoa

I have a NSWindowController where I'm adding my corresponding ViewControllers. I would like to handle touch events in one of the ViewController. My class is
Window.h
#interface WindowController : NSWindowController<NSToolbarDelegate>
#property (nonatomic, strong) NSViewController *currentViewController;
#property (assign) IBOutlet NSView *targetView;
#end
Window.m
-(void)addViewController
{
NSViewController *currentController = [[currentControllerClass alloc]initWithNibName:controllerIdentifier bundle:nil];
self.currentViewController = currentController;
[self.targetView addSubview:self.currentViewController.view];
[self.currentViewController.view setFrame: [self.targetView bounds]];
}
-(void) awakeFromNib{
[super awakeFromNib];
[self.window setIgnoresMouseEvents:NO];
[self setToolbarToPracticeView];
}
-(void)mouseDown:(NSEvent *)theEvent
{
NSLog(#"Window > Mouse down");
}
I'm able to print the mousedown event here. Now I have a viewController, which is
ViewController.h
#interface ViewController : NSViewController
#end
ViewController.m
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
}
return self;
}
-(void)awakeFromNib
{
[self.view setAcceptsTouchEvents:YES];
}
-(BOOL)acceptsFirstResponder
{
return YES;
}
-(void)mouseDown:(NSEvent *)theEvent
{
NSLog(#"ViewController > mouse down");
}
I would like to get the control of the mouseDown in the ViewController. Am I missing something here?
Thanks.

Create a Custom NSView and then delegate a mouseDown method in it's protocol. Import and use the customView in the ViewController and delegate its methods. When you click on the view a mouseDown event triggers.

Not sure of my answer, but if you don't call [super mouseDown:theEvent] in the Window.m (which should be called WindowController.m by the way), you are breaking the responder chain. Hence your view controller can't see it. Am I right?

Related

NSButton does not respond to MouseDown Events if it is a part of custom NSView

I have an OS X application with a main view (i.e., self.view) that contains an NSScrollView and the NSScrollView contains a custom NSView. The custom NSView (flippedNSView) is just an NSView custom class FlippedNSView with a -(BOOL)isFlipped { return YES; }
I have a function that creates a NSButton that is called in the ViewDidLoad.
If button is added to self.view, button works fine
If button is added to flippedNSView, button does not respond.
NSTextViews work fine and respond to MouseDown events, although they do not respond to text selection. Only the buttons do not respond.
Any ideas?
AppDelegate.h
#import "CodeObjViewController.h"
#interface AppDelegate : NSObject <NSApplicationDelegate> {
FlippedView *flippedNSView;
}
// Windows are defined in MainMenu.xib with the following hierarchy
// Window
// |_ View
// |_ flippedNSView
// |_ scrollView
// |_ myCustomView
#property (assign) IBOutlet NSWindow *window;
#property (strong) IBOutlet NSViewController *myViewController;
#property (weak) IBOutlet NSView *myCustomView;
#property(readwrite, strong, nonatomic) IBOutlet NSScrollView* scrollView;
#property(readwrite, strong, nonatomic) IBOutlet FlippedView * flippedNSView;
#end
AppDelegate.m
#import "AppDelegate.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
#synthesize scrollView;
#synthesize flippedNSView;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
_myViewController = [[CodeObjViewController alloc] initWithNibName:#"customViewController" bundle:nil];
[_myCustomView addSubview:[_myViewController view]];
[[_myViewController view] setFrame:[_myCustomView bounds]];
[scrollView setDocumentView: flippedNSView];
}
customViewController.h
#interface FlippedView : NSView
#end
//----------------------------------------------------------
#interface CodeObjViewController : NSViewController {
FlippedView *flippedNSView;
NSScrollView *scrollView;
}
customViewController.m
#implementation FlippedView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
return self;
}
//----------------------------------------------------------
- (BOOL)isFlipped {
return YES;
}
#end
//----------------------------------------------------------
#implementation customViewController
// Custom View Controller
-(void) viewDidLoad {
// The scroll and flipped views are defined on menu.xib and declared on the AppDelegate.
AppDelegate *theAppDelegate = (AppDelegate*) [NSApplication sharedApplication].delegate;
flippedNSView = theAppDelegate.flippedNSView;
scrollView = theAppDelegate.scrollView;
NSButton *button = [[NSButton alloc] initWithFrame:NSMakeRect(0,0,10,10)];
[self.view addSubview:helpButton]; // THIS RESPONDS TO MOUSE DOWN
[flippedNSView addSubview:helpButton]; // THIS DOES NOT RESPOND TO MOUSE DOWN
[button setAction:#selector(buttonClicked:)];
[button setTarget:self];
}
-(void) buttonClicked : (id) sender {
NSLog(#"Button clicked");
}

How do you make an NSTextField in a custom View be the first responder?

It's my understanding that making the textfield the first responder means that you can enter text into the textfield without having to click on the textfield first.
I made a simple app to demonstrate the problem. Here are the files:
//
// AppDelegate.m
// MyFirstResponderApp
//
#import "AppDelegate.h"
#import "MyViewController.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property(strong) MyViewController* viewCtrl;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
MyViewController* myViewCtrl = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
[self setViewCtrl:myViewCtrl];
[[self window] setContentSize:[[myViewCtrl view] bounds].size];
[[self window] setContentView:[myViewCtrl view]];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
#end
...
//
// MyViewController.h
// MyFirstResponderApp
//
#import <Cocoa/Cocoa.h>
#interface MyViewController : NSViewController
#property IBOutlet NSTextField* textField;
#end
...
//
// MyViewController.m
// MyFirstResponderApp
//
#import "MyViewController.h"
#interface MyViewController ()
#end
#implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do view setup here.
[[self textField] becomeFirstResponder]; //I tried here...
}
//And overriding initWithNibName:bundle: and setting it here:
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
[[self textField] becomeFirstResponder];
}
return self;
}
#end
The File's Owner of MyView.xib is MyViewController, and here are the File's Owner outlets:
Response to answer:
1) This works for me:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
MyViewController* myViewCtrl = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
[self setViewCtrl:myViewCtrl];
[[self window] setContentSize:[[myViewCtrl view] bounds].size];
[[self window] setContentView:[myViewCtrl view]];
if ([[myViewCtrl textField] acceptsFirstResponder]) {
[[[myViewCtrl textField] window] makeFirstResponder:[myViewCtrl textField]];
}
}
3) Yes, I am trying to do the first responder dance for the initial showing of the window, but this doesn't work for me:
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
MyViewController* myViewCtrl = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
[self setViewCtrl:myViewCtrl];
[[self window] setContentSize:[[myViewCtrl view] bounds].size];
[[self window] setContentView:[myViewCtrl view]];
/*
if ([myViewCtrl acceptsFirstResponder]) {
[[[myViewCtrl textField] window] makeFirstResponder:[myViewCtrl textField]];
}
*/
[[self window] setInitialFirstResponder:[myViewCtrl textField]];
}
The window displays fine, but I have to click on the textfield in order to enter text. If I use makeFirstResponder: instead, then the textfield operates like I want:
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
MyViewController* myViewCtrl = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
[self setViewCtrl:myViewCtrl];
[[self window] setContentSize:[[myViewCtrl view] bounds].size];
[[self window] setContentView:[myViewCtrl view]];
/*
if ([[myViewCtrl textField] acceptsFirstResponder]) {
[[[myViewCtrl textField] window] makeFirstResponder:[myViewCtrl textField]];
}
*/
[[self window] makeFirstResponder:[myViewCtrl textField]];
}
I found that suggestion at Apple Developer's Event Handling Basics, section Setting the First Responder.
Response to comment under answer:
In my app, if I select MainMenu.xib then uncheck Visible at Launch, the code below successfully makes the textfield in the View the First Responder:
//
// AppDelegate.m
// MyFirstResponderApp
//
#import "AppDelegate.h"
#import "MyViewController.h"
#interface AppDelegate ()
#property(weak)IBOutlet NSWindow* window;
#property(strong) MyViewController* viewCtrl;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
MyViewController* myViewCtrl = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
[self setViewCtrl:myViewCtrl];
[[self window] setContentSize:[[myViewCtrl view] bounds].size];
[[self window] setContentView:[myViewCtrl view]];
[[self window] setInitialFirstResponder:[myViewCtrl textField] ];
[self.window makeKeyAndOrderFront:self];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
#end
If I create a WindowController (along with a .xib, and delete the window in MainMenu.xib), then it makes the most sense to me to organize the configuration/initialization like this:
//
// AppDelegate.m
// MyFirstResponderApp
//
#import "AppDelegate.h"
#import "MyViewController.h"
#import "MyWindowController.h"
#interface AppDelegate ()
#property(strong) MyWindowController* windowCtrl;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[self setWindowCtrl:[[MyWindowController alloc]
initWithWindowNibName:#"MyWindow"]];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
#end
Then setup the View by overriding initWithWindowNibNamein the WindowController:
//
// MyWindowController.h
// MyFirstResponderApp
//
#import <Cocoa/Cocoa.h>
#interface MyWindowController : NSWindowController
-(instancetype)initWithWindowNibName:(NSString *)windowNibName;
#end
...
//
// MyWindowController.m
// MyFirstResponderApp
//
#import "MyWindowController.h"
#import "MyViewController.h"
#interface MyWindowController ()
#property(strong) MyViewController* viewCtrl;
#end
#implementation MyWindowController
-(id)initWithWindowNibName:(NSString *)windowNibName {
if (self = [super initWithWindowNibName:windowNibName]) {
MyViewController* myViewCtrl = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
[self setViewCtrl:myViewCtrl];
[[self window] setContentSize:[[myViewCtrl view] bounds].size];
[[self window] setContentView:[myViewCtrl view]];
[[self window] setInitialFirstResponder:[myViewCtrl textField] ];
[self showWindow:self]; //I had to uncheck 'Visible at Launch' for the WindowController's window in the Attributes inspector
}
return self;
}
- (void)windowDidLoad {
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
#end
...
//
// MyViewController.h
// MyFirstResponderApp
//
#import <Cocoa/Cocoa.h>
#interface MyViewController : NSViewController
#property(weak) IBOutlet NSTextField* textField;
#end
A couple of things:
You should never call -becomeFirstResponder yourself (except to call through to super in an override). It says right in the docs:
Never invoke this method directly.
The method is the system informing an object that it's about to become the first responder and giving it a chance to refuse. It doesn't actually make the object the first responder.
The way to make a view the first responder is to first check that it will accept that status and then tell the window to make it the first responder:
if ([self.textField acceptsFirstResponder])
[self.textField.window makeFirstResponder:self.textField];
You are currently trying to make the text field the first responder during initialization of the containing view's view controller. This is too early. The view can't be in a window at this point. So, it can't be a window's first responder, yet. You should do this after the view has been added to a window.
That job rightfully belongs in a window controller, not a view controller. After all, it's only the window controller which has the overview necessary to decide which of its (possibly many) views should be the first responder. You aren't using a separate window controller in this simple app, so the responsibility would fall to the AppDelegate class. (It's effectively acting as the window's controller, given the code you posted.)
If this is happening before the window is shown, you should consider setting the window's initialFirstResponder rather than forcing a view to be the first responder immediately. The window will make its initialFirstResponder the first responder when it is first shown.
It's better to reserve -makeFirstResponder: for programmatically changing the first responder after the window has been shown.
setting First Responder immediately during initialization can be ignored. especially for a UI component attatched to View Controller other than Window Controller.
There might be a delay between viewWillAppear() and viewDidAppear().
it can be done with window's initialFirstResponder property, or makeFirstResponder() medthod like...
window?.initialFirstResponder = yourVC.yourTextField
//OR
window?.makeFirstResponder(yourVC.yourTextField)
(Sorry, My brain and hands have deprecated Objective-C )
FYI, the window property can be accessed various ways in some places.
in your custom VC, it would be:
self.yourTextField.window?
//OR
NSApplication.sharedApplication().mainWindow?
In your custom WindowController:
self.window?
//or
NSApplication.sharedApplication().mainWindow?
In your Appdelegate.swif :
window? //if you use that default property in the template.
//if you have your custom Window Controller instance 'mainWindowController' :
self.mainWindowController.window?
//if you have a custom VC loaded to custom WC, well, :
self.mainWindowController.yourVC.window?
//OR just use
NSApplication.sharedApplication().mainWindow?
but like I said, it can be ignored during luanching if you set it immediately. So you set it after delay:
window?.performSelector(#selector(window?.makeFirstResponder(_:)),
withObject: .yourTextField,
afterDelay:0.5)
Depending on where you put this, the dot notations of 'window?' and '.yourTextField' parts should be different.

Setting dataSource for NSTableView across scenes in a storyboard

I am using XCode 6 on OS X 10.10 and have a storyboard containing a window with a split view controller, as shown in the following image.
The split view controller (highlighted in the image) is an instance of MyViewController, which has the following code:
MyViewController.h
#import <Cocoa/Cocoa.h>
#interface MyViewController : NSSplitViewController <NSTableViewDataSource>
#end
MyViewController.m
#import "MyViewController.h"
#implementation MyViewController
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return 7;
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
return [NSString stringWithFormat:#"%ld", (long)row];
}
#end
I would like to make the view controller the dataSource of the NSTableView in my storyboard, however I am unable to connect them. Is there a reason for this?
In your NSSplitViewController-subclass viewDidLoad-method set the data source programmatically. You need to implement the child view controller class as well (with the tableView outlet connected to the control).
MySplitViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
for (NSSplitViewItem *item in self.splitViewItems)
{
NSViewController *controller = item.viewController;
if ([controller isKindOfClass:[MyChildController class]])
{
MyChildController *myController = (MyChildController *)controller;
myController.tableView.dataSource = self;
[myController.tableView reloadData];
}
}
}
But to tell you the truth I don't like this approach. It's more better when the table view's data source methods are in the native view controller class.
Another way to do that. MyChildController.h file:
#class MyChildViewController;
#protocol MyChildControllerDelegate <NSObject>
- (void)childController:(MyChildViewController *)controller didSelectRowAtIndex:(NSUInteger)index;
#end
#interface MyChildViewController : NSViewController <NSTableViewDataSource, NSTableViewDelegate>
#property (nonatomic, weak) id<MyChildControllerDelegate> delegate;
#property (nonatomic, retain) NSArray *items;
#property (nonatomic, weak) IBOutlet NSTableView *tableView;
#end
Don't forget to implement all the table view dataSource and delegate methods you need. MySplitViewController.m file:
- (void)viewDidLoad
{
[super viewDidLoad];
for (NSSplitViewItem *item in self.splitViewItems)
{
NSViewController *controller = item.viewController;
if ([controller isKindOfClass:[MyChildController class]])
{
MyChildController *myController = (MyChildController *)controller;
myController.delegate = self;
[myController setItems:_items];
}
}
}

Cocoa : Load NSViewController with nib

I have tried many possibilities I found on this site and read some explanations
from apple developer page, but seems like i couldn't resolve my problem about
loading NSViewController with/form NIB.
Files on Xcode Project look a bit like this :
SecondViewController.h
SecondViewController.m
SecondViewController.xib
AppDelegate.h
AppDelegate.m
MainMenu.xib
The main problem is how could i create the SecondViewController programatically with
the initial nib on SecondViewController.xib
Custom class of FileOwner on MainMenu.xib is NSApplication
Custom class of FileOwner on SecondViewController.xib is SecondViewController
There are some panels and window in MainMenu.xb (about window and preference panel)
This application has no main window (using notification icon on status bar)
SecondViewController.h
#interface SecondViewController : NSViewController {
BOOL fullScreenMode;
NSImageView *fullScreenbg;
NSImageView *imageView1;
NSImageView *imageView2;
NSPanel *imageWindow;
}
#property (assign) IBOutlet NSImageView *fullScreenbg;
#property (assign) IBOutlet NSImageView *imageView1;
#property (assign) IBOutlet NSImageView *imageView2;
#property (assign) IBOutlet NSPanel *imageWindow;
SecondViewController.m
#implementation SecondViewController {
NSImageView *nv1;
NSImageView *nv2;
NSSize curImgSize;
}
#synthesize fullScreenbg;
#synthesize imageView1;
#synthesize imageView2;
#synthesize imageWindow;
......
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
fullScreenMode = YES;
}
return self;
}
AppDelegate.h
#interface AppDelegate : NSObject <NSApplicationDelegate,NSWindowDelegate> {
NSPanel *aboutWindow;
IBOutlet NSMenu *myStatusMenu;
IBOutlet NSMenuItem *toggleFullScreen;
}
AppDelegate.m
#implementation AppDelegate {
SecondViewController *controller;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
controller = [[SecondViewController alloc]
initWithNibName:#"SecondViewController"
bundle:nil];
//Not work (fullscreenbg, imageView1, imageView2,imageWindow = nil)
//controller = [[SecondViewController alloc] init]; ?? <-- didn't work either
}
Even if using initWithNibName or just init, all the IBOutlet properties seems to be nil
on debug.
i've tried other solustions like "NSBundle loadNibNamed" or using loadView but it didn't work (warning message : "NSObject my not respond to loadView").
The main purpose of the secondViewController is to display notification message including
graphics and web element.
I hope someone could give me a best suggestion. Thanks.
This is normal behavior. IBOutlets are not connected in the constructor.
You can override viewDidLoad, call super and then do any initialization.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
//added this line :
if (nibBundleOrNil!=nil || ![nibBundleOrNil isEqualtoString:#""]) {
[NSBundle loadNibNamed:#"SecondViewController" owner:self];
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
fullScreenMode = YES;
}
return self;
}

Why doesn't my NSView delegate method gets called?

I'm working on my first Mac app and I'm having some trouble with getting my NSView and it's delegate methods to work. I have a NSViewController that should response to mouse events, i.e mouseDown. This didn't work, instead I created a custom NSView subclass that looks like this:
// Delegate method
#protocol CanvasViewDelegate
- (void)mouseDown:(NSEvent *)event;
#end
#interface CanvasView : NSView
{
id delegate;
}
#property (nonatomic, strong) id delegate;
#end
#implementation CanvasView
#synthesize delegate;
- (void)mouseDown:(NSEvent *)event
{
[delegate mouseDown:event];
}
The NSViewController relevant parts that should act as the NSView's delegate looks like this:
#import "CanvasView.h"
#interface PaintViewController : NSViewController <CanvasViewDelegate>
{
CanvasView *canvasView;
}
#property (strong) IBOutlet CanvasView *canvasView;
#end
#synthesize canvasView;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
canvasView = [[CanvasView alloc] init];
canvasView.delegate = self;
}
return self;
}
- (void)mouseDown:(NSEvent *)event
{
NSLog(#"Down");
}
Now, the method mouseDown doesn't get called, what am I doing wrong?
If you want an NSView subclass to accept events, you will have to implement:
- (BOOL)acceptsFirstResponder {
return YES;
}
as documented here.
Creating the view programmatically instead did work. Like this:
NSRect canvasRect = self.view.frame;
canvasView = [[CanvasView alloc] initWithFrame:canvasRect];
[self.view addSubview:canvasView];
canvasView.delegate = self;

Resources