Adding NSTouchBar support after main window has been created - objective-c

I'm trying to add support for exposing NSTouchBar buttons via a plugin to an application that I cannot otherwise modify. The plugin, a shared library, is loaded at runtime after the main window has been created. I've created an AppDelegate as follows:
#interface AppDelegate : NSResponder <NSTouchBarDelegate>
#end
With an #implmentation that implements the makeTouchBar and touchBar functions:
- (NSTouchBar *) makeTouchBar
- (nullable NSTouchBarItem *) touchBar:(NSTouchBar *)touchBar
makeItemForIdentifier: (NSTouchBarItemIdentifier)identifier
I finally try to inject it into the application, I have the following in the onload function that's called in the dynamic library when it's loaded into the application.
NSWindow *w = [NSApp mainWindow];
AppDelegate *a = [[AppDelegate alloc] init];
a.nextResponder = w.nextResponder;
w.nextResponder = a;
// Re-setting FirstResponder will trigger AppDelegate::makeTouchBar
// that was attached above.
NSResponder *n = w.firstResponder;
[w makeFirstResponder: nil];
[w makeFirstResponder: n];
however... AppDelegate::touchBar will never be called, thus the touchbar will never be populated with my test buttons.
If I create a new project in Xcode, and use the exact same AppDelegate implementation (copy-paste of the same #interface and #implementation), I get functional buttons that are both shown and responds to press events, so it's the injection part that seems to be broken. (In Xcode everything is hooked up via MainMenu.xib I guess)
Update:
The main problem was that the application was compiled in a way that prevented TouchBar to work. Perhaps built on an older version of Mac OS.

If this object really should act as the application delegate (as the name suggests) -- rather than inserting it in the main window's responder chain, it would be better to actually set it as the NSApp's delegate. The set delegate's touchBar is also used when building the touch bar (see NSTouchBar Discovery and the Responder Chain).
If that doesn't work for your plugin (maybe the application already has a specific app delegate it needs), you could insert it into NSApp's responder chain instead of the mainWindow's.
Both of these also have the benefit of making the touch bar be associated at the application level rather than window. So if the main/key window changes, the provided touch bar will always be used.

Related

initWithWindow: in window controller not get called

I'm new to Cocoa programming.
In Xcode 6.1 I created an OS X app using storyboard. There's a Window Controller in the Outline out of the box. I changed its class in the "Identity Inspector" to a customized subclass of NSWindowController, which is named WindowController.
I wrote the following code in the #implementation of WindowController:
- (instancetype)initWithWindow:(NSWindow *) window {
NSLog(#"window");
window.titleVisibility = NSWindowTitleHidden;
return [super initWithWindow:window];
}
But the Output panel shows nothing.
if I override the windowDidLoad method in the class,
- (void)windowDidLoad {
[super windowDidLoad];
self.window.titleVisibility = NSWindowTitleHidden;
NSLog("#Cool...");
}
The message shows up.
The doc of NSWindowController says that -initWithWindow: is the Designated Initiailzer of the class. How can it be skipped?
In the How Window Controllers Work section of Window Programming Guide, it says:
For simple documents—that is, documents with only one nib file containing a window—you need do little directly with NSWindowController objects. AppKit creates one for you. However, if the default window controller is not sufficient, you can create a custom subclass of NSWindowController.
That's exactly what I am doing. But I don't know what I'm missing here.
Thanks!
As far as I know you should first call super with initWithWindow and then set the title. Otherwise the call to super initializes the window without the previous settings.
When you override the windowDidLoad method, you set the title visibility after initialization and everything is okay.

How to open an NSWindowController from another NSWindowController in Cocoa

I'm developing an app in cocoa for MacOSX in Xcode5 and I want to open another window from my current window by pressing a button, this is my code:
- (IBAction)openWindow:(id)sender {
WindowController *controllerWindow = [[WindowController alloc] initWithWindowNibName:#"WindowController"];
[controllerWindow showWindow:nil];
[[controllerWindow window] makeMainWindow];
}
so far I can see the window appearing by one second and then this just dissappear, how to do this correctly???
Neither the window nor the window controller have a strong reference anywhere outside the scope of this method.
So after that, they're getting released.
Normally, you would add your window controller to some container like an array in your app delegate.
The array will retain the window controller.
The window controller can hang on to the window.
It also makes sense for the action method to be in the app delegate. You button should just send a selector up the responder chain.
use this..
Create a new .h & .m files which yo need to open, as NewWindowController (for eg.) along with its .xib
And on any button click, to open the newly defined window, just allocate, instantiate and present..
NewWindowController *controllerWindow = [[NewWindowController alloc] initWithWindowNibName:#"NewWindowController"];
[controllerWindow showWindow:self];

Changes not reflected across view when using binding in cocoa

I am creating some sample applications to understand the concepts of view navigation, binding etc in cocoa.
Here is the scenario:
I have a window that has a tab view(2 tabs) in MainMenu.Xib.
I have a text field in the first tab and label in the second tab. I want both of them to reflect the same value and I want to do this using binding. Also, I don't want to use the views provided to me along with the tab view.
These are the steps I have done.
The view of each tab view item is set separately in the applicationDidFinishLaunching: method using the following code:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
//initialize view controllers
view1=[[ViewTab1 alloc] initWithNibName:#"ViewTab1" bundle:nil];
view2=[[ViewTab2 alloc] initWithNibName:#"ViewTab2" bundle:nil];
//set views
[[[myTabView tabViewItems] objectAtIndex:0]setView:view1.view];
[[[myTabView tabViewItems] objectAtIndex:1]setView:view2.view];
}
myTabView is the outlet reference of the tab view from MainMenu.xib in AppDelegate.
ViewTab1 is the name of the first view controller (and the xib).
ViewTab2 is the name of the second view controller (and the xib).
ViewTab1 has one single text field (and an associated label). I have bound this to a variable(name) declared in AppDelegate.
ViewTab2 has a label. I have bound this also to the same variable in AppDelegate.
The variable, 'name' is initialized in the init method of AppDelegate.
AppDelegate.h
....
NSString *name;
....
#property(strong) ViewTab1 *view1;
#property(strong) ViewTab2 *view2;
#property (assign) IBOutlet NSTabView *myTabView;
#property (strong) NSString *name;
....
AppDelegate.m
....
#synthesize myTabView;
#synthesize view1,view2;
#synthesize name;
....
- (id)init {
self = [super init];
if (self) {
name=#"dummy";
}
return self;
....
Apart from this I haven't done any coding in my program.
In the ViewTab1.xib I got an object and made it an instance of AppDelegate and then connected the delegate reference of the Application object(NSApplication) to the same object. (I hope this is the right way of getting the AppDelegate object.)
I did the same in ViewTab2.xib
Then I bound the text field in ViewTab1 and label in ViewTab2 to this variable in AppDelegate.
When I run the program both the text field and label shows "dummy". But when I change the value in the text field, its not reflected in the label in the second tab( i.e. ViewTab2).
Please tell me what I'm doing wrong.
How to establish binding to the same App delegate object from any loaded Nib?
Yes, I know this frustrated situation as described in question... after many weeks and hundreds pages of documentation for KVO - Notifications - Bindings I think there is one very simple solution for that.
As we can find in some information sources the nib-loading process produce new instances of members... and we need to use binding connection to the old one.
Note that bindings made in InterfaceBuilder are redirect to these new instances automatically after loading nib
Why not redirect the pointer of App delegate to the old instance?
In method where you loads your nib you can test which object is app delegate before and just after nib load.
If the new one isn’t the same as the previous one you can redirect it as you want.
This simple example works for me in Xcode3 under 10.5.8 with target to OSX10.5 / i386:
// ***** SOMEWHERE IN DEFAULT APP-DELEGATE.m IMPLEMENTATION
- (IBAction) createOtherWindowFromNib: (id)sender
{
// ensure that app delegate is set as you want...
[NSApp setDelegate:self];
NSLog(#"APP-DELEGAT **** CREATE-TEST-WINDOW ***** WHO IS APP-DELEGATE BEFORE NIB LOAD: %# ", [[NSApp delegate] description]);
// we can bind members of the nib to this controller over proxy object named "File’s Owner"
NSWindowController *otherWinCapo = [[NSWindowController alloc] initWithWindowNibName: #"OtherTestWindow"];
NSLog(#"APP-DELEGAT **** CREATE-TEST-WINDOW ***** WHO IS APP-DELEGATE AFTER NIB LOAD: %# ", [[NSApp delegate] description]);
// make some test for delegates before/after here if you need ...
// usually your bindings made inside "OtherTestWindow.xib" by IB doesn’t works in this moment
// ... and some redirection if needed
[NSApp setDelegate:self];
// afer that the bind made in IB inside "OtherTestWindow.xib"
// referred to (proxy object) "Application.delegate.myBOOL" (Bind to:Application, Model Key Path:delegate.myBOOL)
// react to changes of myBOOL placed in default app delegate object as expected
// simultaneously in every open instance of "OtherTestWindow.xib"
[otherWinCapo showWindow: otherWinCapo.window]; // we need populate the window instance on screen to see it
}
I think the problem is that the objects in your xibs that you set to the app delegate class create 2 different instances of the app delegate, so changing the value of the text field changes the value of name in one instance but not in the other. That's what you're doing wrong, unfortunately, I can't think of a solution at this time.
Have you turned on 'Continuously Updates Value' in the NSTextField controls?
See this example.

How to get Main Window (App Delegate) from other class (subclass of NSViewController)?

I'm trying to change my windows content, from other class , that is subclass of NSViewController.I'm trying code below, but it doesn't do anything.
[NSApplication sharedApplication]mainWindow]setContentView:[self view]]; //code in NSViewController
[NSApplication sharedApplication]mainWindow] // returns null
I tried to add
[window makeMainWindow];
in App Delegate class, but it won't help.
Did I miss something?
P.S. Also I'm using code below to call any delegate function in my class,
[(appDelegate *) [[NSApplication sharedApplication]delegate]MyMethod];
but I wonder is there something better, wihtout importing delegate class. Something like this
[[NSApplication sharedApplication]delegate]MyMethod];
(it gives warning)
For the mainWindow method the docs say:
This method might return nil if the application’s nib file hasn’t finished loading, if the receiver is not active, or if the application is hidden.
I just created a quick test application and I placed the following code:
NSLog(#"%#", [[NSApplication sharedApplication] mainWindow]);
into my applicationDidFinishLaunching:aNotification method, and into an action method which I connected to a button in the main window of my application.
On startup, the mainWindow was nil, but when I click the button (after everything is up and running and displayed), the mainWindow was no longer nil.
NSApplication provides other methods which you may be useful to you:
- windows - an array of all the windows;
– keyWindow - gives the window that is receiving keyboard input (or nil);
– windowWithWindowNumber: - returns a window corresponding to the window number - if you know the number of the window whose contents you wish to replace you could use this;
– makeWindowsPerform:inOrder: - sends a message to each window - you could use this to test each window to see if it's the one you are interested in.
With regard to calling methods on the delegate, what you say gives a warning works fine for me. For example, this works with no warnings:
NSLog(#"%#", [[[NSApplication sharedApplication]delegate] description]);
What exactly is the warning you receive? Are you trying to call a method that doesn't exist?
Fighting with MacOS just figured this out.
Apple's quote:
mainWindow
Property
The app’s main window. (read-only)
Discussion
The value in this property is nil when the app’s storyboard or nib file has not yet finished loading. It might also be nil when the app is inactive or hidden.
If you have only one window in your application (which is the most used case) use next code:
NSWindow *mainWindow = [[[NSApplication sharedApplication] windows] objectAtIndex:0];
Promise it won't be nil, if application has windows.
Swift flavored approaches for getting the main window (if present)
Application Main Window
guard let window = NSApplication.shared.mainWindow,
else {
// handle no main window present
}
// ... access window here
Application Window Array
let windowArray: [NSWindow] = NSApplication.shared.windows
guard windowArray.count > 0 else {
// hand case where no windows are present
}
let window = windowArray[0]
// ... access window here
If the window property isn't set yet, try delaying things until the app has finished loading, like so:
[myObject performSelector:#selector(theSelector) withObject:nil afterDelay:0.1];

Objective C Delegate for the Main Application Window

So I'm trying to do this exercise where I need to set a delegate for the main window. The purpose is to make sure that when the user resizes the window, it's always twice as wide as it is high.
This is my AppController.h file:
#import <Cocoa/Cocoa.h>
#interface AppController : NSObject
{
NSWindow *windowWillResize;
}
#end
and this is my AppController.m file:
#import "AppController.h"
#implementation AppController
- (id) init
{
[super init];
windowWillResize = [[NSWindow alloc] init];
[windowWillResize setDelegate:self];
return self;
}
- (NSSize) windowWillResize:(NSWindow *)sender
toSize:(NSSize)frameSize;
{
NSLog(#"size is changing");
return frameSize;
}
#end
However, I can remove the line
[windowWillResize setDelegate:self];
since I set the delegate in Interface Builder, but I'm not sure why this works.
How does windowWillResize know that I'm referring to the main application window since I'm doing a completely new
windowWillResize = [[NSWindow alloc] init];
I have a feeling that I am completely doing this wrong. Could someone point me in the right direction? Thanks!
Indeed, you don't need to create a NSWindow *windowWilResize since a newly created Cocoa app already has a main window. You don't need to implement an -init method either.
You only need to set you appController as a delegate of your main window in Interface Builder and to implement the -windowWillResize: method in your appController.
If you are familiar with french language, you can take a look at a blog entry I have written on this subject: Délégation en Cocoa.
You're leaking an instance of NSWindow. In -init you create an NSWindow instance. However, that is not used because when the NIB loads, it sets up all the connections that you specified in Interface Builder and you start using the window from the NIB instead. Do not create a window object in code - Interface Builder does it for you! :-)
In fact, it's not quite "instead"; your app controller is now the delegate for both NSWindow instances - the one that comes from the NIB and the one you instantiated in -init. However as the in-code NSWindow is never used anywhere else, it's still redundant and should be removed.
If you just want to maintain the aspect ratio of the window you can use either of these two NSWindow methods:
setAspectRatio:(NSSize)
setContentAspectRatio:(NSSize)
The first method locks the entire window size, including the title bar. The second one just the content. You can call this method during the initialization of your window inside the delegate (for example: -applicationDidFinishLaunching)