Maybe I'm missing something really simple here (I hope so), but is there a trick to changing the title of an NSPanel at runtime? The obvious [panel setTitle:#"New title"] doesn't seem to be working.
I'm trying to display a regular panel which contains a WebView, and I want to the title of the panel to reflect the title of the HTML content.
I subclassed NSWindowController and called initWithWindowNibName. I changed the class in the nib from NSWindow to NSPanel, and everything seems to be working okay. In my window controller, I did this:
- (void)windowDidLoad {
[super windowDidLoad];
[[self window] setTitle:#"My New Title"];
}
(I will actually be setting the title in a webView:didReceiveTitle:forFrame delegate, but this is simpler to show).
I verified that the code is getting called, and there are no errors reported, but the title never changes. Any ideas?
In the nib file that contains the panel, make sure that the File’s Owner’s class has been set to your NSWindowController subclass and that the window outlet from File’s Owner has been connected to the panel. Otherwise, the window controller won’t know which window it should be managing and [self window] returns nil.
You're setting the title, but you're not telling the window to display itself again.
Related
My app is not document based, and its sole window is managed by a custom, xib-based NSWindowController subclass that I instantiate within the app delegate code:
- (void) applicationDidFinishLaunching:(NSNotification*) aNotification
{
_mainWindowController = [MainWindowController new];
// (stored in ivar just to prevent deallocation)
//[_mainWindowController showWindow:self];
// ↕︎ Not sure about the difference between these two... both seem to work.
[[_mainWindowController window] makeKeyAndOrderFront:self];
}
I have subclassed NSClipView to "center content inside a scroll view" (instead of having it pegged to the lower left corner) when it is zoomed to a size smaller than the clip view, and also implement custom functionality on mouse drag etc.
My window does have a title bar.
My window isn't borderless (I think), so I am not subclassing NSWindow.
I have overriden -acceptsFirstResponder, -canBecomeKeyView and -becomeFirstResponder in my NSClipview subclass (all return YES).
The drag events do trigger -mouseDown: etc., and if I set a breakpoint there, the first responder at that point is the same as the window hosting my clip view: [self.window firstResponder] and [self window] give the same memory address.
What am I missing?
Update
I put together a minimal project reproducing my setup.
I discovered that if my custom view is the window's main view, -keyDown: is called without problems. But if I place a scroll view and replace its clip view by my custom view (to do that, I need to change the base class from NSView to NSClipView, of course!), -keyDown: is no longer triggered.
I assume it has something to do with how NSScrollView manages events (however, as I said before, -mouseDown:, -mouseDragged: etc. seem to be unaffected).
I also discovered that I can override -keyDown: in my window controller, and that seems to work, so I have decided to do just that (still open to an answer, though). Also, since I'm trying to detect the shift key alone (not as a modifier of another key), I'd rather use:
- (void) flagsChanged:(NSEvent *) event
{
if ([event modifierFlags] & NSShiftKeyMask) {
// Shift key is DOWN
}
else{
// Shift key is UP
}
}
...instead of -keyDown: / -keyUp: (taken from this answer).
Simple structure:
exampleController.h :
#import <Foundation/Foundation.h>
#interface exampleController : NSWindowController {
#public
IBOutlet NSPanel *entryPanel;
#property (nonatomic, strong) IBOutlet NSPanel *entryPanel;
#end
exampleController.m :
#import "exampleController.h"
#implementation exampleController
#synthesize entryPanel;
- (id)init {
self = [super initWithWindowNibName:#"ExamplePanel"];
if (self) {
// Initialization code here.
NSLog(#"entryPanel: %#", entryPanel);
[self.entryPanel setTitle:#"TESTING!"];
}
return self;
}
randomController.m :
...
- (id) init {
self = [super init];
if (self) {
// loading our example controller if it isn't loaded yet.
if (!ourExampleController) {
ourExampleController = [exampleController alloc] init];
}
}
return self;
}
...and then later in the random controller within a method I show the NSPanel via:
[ourExampleController showWindow:self];
[ourExampleController window] makeKeyAndOrderFront:self];
My problem is that no matter what, the first time the NSPanel displays and shows itself the title is always still set to the title that it has in Interface Builder! Even though I explicitly set the title in the exampleController init method.
I've also tried throwing an NSLog(#"entryPanel: %#", entryPanel) in the init method for exampleController and at launch it is always NULL. I do not have to ALLOC all my IBOutlets in the init because I am already synthesizing them?
I've double checked everything in interface builder. The File Owner for the ExamplePanel.xib is set to the exampleController class. The window AND entryPanel outlets are both referencing the NSPanel in our xib file. What am I missing ??
Thanks in advance!
EDIT: Just to add. If I open the window (..and see the default IB title) and then close it and reopen it with a method that changes the title - it seems to work! This problem seems to only reside with the window first opening. It seems like my properties are not being alloc'd until the window first opens?
EUREKA!
As per discussion here:
IBOutlet instances are (null) after loading from NIB
I learnt that the window itself is not loaded when my controller is initialized. Found that surprising since I figured using initWithWindowNibName:#"myNibFile" would also alloc and initialize all outlet properties but since I'm new to OSX Obj-C that appears to not be the case. All the outlet properties are only alloc'd once the window itself is loaded too.
It's easy to just show the window (which also loads the window if it's not loaded yet) and then quickly set all the outlets to my desired values BUT this was an issue for me since I wanted to avoid that ever so slight "screen flicker" (for lack of a better description) that occurs as the values adjust to their new settings.
The solution was to find a way to load the controller and load the window without actually showing it first! Then I discovered this:
Can you force a NSWindow to load, i.e. before it is presented onscreen?
Steps to make that happen:
Add the following to my NSWindowController subclass init method:
// this loads the window as per link/description above
[self window]
The key seems to be though to ensure that in your NIB/XIB file that the Visible At Launch is unchecked. If it is checked (default behavior) then the [self window] call above will still show your window when your app launches. Unchecking the above option ensures the above call does not show your window until you explicitly show it yourself!
E.g. You can define an action button which loads your window:
[exampleController showWindow:self];
[[exampleController window] makeKeyAndOrderFront:self];
Hope this helps someone else out. Was a head scratcher for a couple hours!
You should set the title, etc. in -awakeFromNib or -windowDidLoad instead of an -init… method. That way the values will be set before the window is shown and you won't get the flicker.
Okay so I feel like there's something obvious I'm missing in this question. I've used makeFirstResponder throughout my code to move from textField 1 to 2, 2 to 3, etc. That seems to work as I want it to, yet when the new view is loaded, I want the cursor to be in textField1, and yet the following code does not place the cursor in textField1 upon load.
- (void) awakeFromNib{
[[[self view] window] makeFirstResponder:textField1];
}
I also tried setInitialFirstResponder, and that didn't have any effect either (I don't even think that would be right.) So, is it because it is in the awakeFromNib method? Can anyone tell me what I'm missing? Thanks in advance.
EDIT - My solution was differed slightly from the accepted answer so I thought I'd post my implementation. Because the view I wanted to set the first responder for was a subview added later (think the second screen of an application wizard), I simply added a setCursorToFirstTextField method:
- (void) setCursorToFirstTextField {
[[[self view] window] makeFirstResponder:textField1];
}
And made sure to call it after I had added the subview to the custom view on the original window.
Yes, you're right about the problem being the location of the method in awakeFromNib. If you log [self.view window] in your awakeFromNib, you'll see that it's NULL. I don't know how exactly you have things set up, but I'm guessing (if this relates to your WizardController question) that you're doing an alloc initWithNibName:bundle: in another class to create your view controller and then adding that controller's view to the view hierarchy. If you throw some logs in there, it will show you that awakeFromNib in the controller class is called after the alloc init, but before the view is added as a subview, so there is no window at that time. The way I got around this problem was to create a setup method in the view controller class (with the makeFirstResponder code in it), and call it from the class where you create the controller after you add it as a subview.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.wizard = [[WizardController alloc] initWithNibName:#"WizardController" bundle:nil];
[self.window.contentView addSubview:wizard.view];
[self.wizard doSetup];
}
In my application window I have two NSViews. On the left the NSView ("Menu") contains a few buttons. When one of the buttons is clicked it should change the contents of the right NSView ("Content").
For each of the views on the right I have a separate NSViewControllers that get loaded and their views gets added as a subview. When a further button gets pressed on the left the added subviews on the right should be removed and the new view should be loaded as a subview.
To accomplish this I load my Menu in AppDelegate with the following:
MenuVC *menuSubView = [[MenuVC alloc] initWithNibName:#"MenuVC" bundle: nil];
menuSubView.contentView = (NSView*)[self contentView];
[[self menuView] addSubview:[menuSubView view]];
This works fine. As you can see I have a NSView pointer in the Menu VC which points to the contentView so that I can populate it with the subviews.
Now as a method for one of the button presses I do the following:
SomeContentVC *subView = [[SomeContentVC alloc] initWithNibName:#"SomeContentVC" bundle:nil];
[self.contentView addSubview:[subView view]];
This does not work.
If I however add a subview from the awakeFromNib method of the MenuViewController implementation (in the case of default content when the app opens) it works. However when I try to remove that subview using
[[self.contentView setSubviews:[NSArray array]];
I can't. Interesting is also that if I try to count the number of subviews (even after having added one in the awakeFromNib method) it returns 0 subviews for self.contentView. Why? How can I get it to work properly?
Thanks
The fact that messaging self.contentView achieves nothing except, for some things, returning 0 probably means that self.contentView is nil.
Do you perhaps have two instances of MenuVC by accident? Perhaps one instantiated in a NIB and one instantiated in code?
When in doubt, log everything. Log self in various methods. Log menuSubView just after you create it. Log menuSubView.contentView just after you assign it. Etc. Eventually, you'll probably see that you're interacting with different objects than you thought you were.
I have an NSCollectionView with a bunch of NSViews in it, stacked vertically, to make it look a bit like UIKit's UITableView. Everything works as expected, except for one thing:
When right-clicking any one of the NSViews, I expect the NSMenu I set to be view's menu to be shown, but alas - nothing happens.
The crazy part is all the right methods are being called, exactly as could be expected: -rightMouseDown:, -menuForEvent: and finally -menu.
When I set up any object as the NSMenu's delegate, menuWillOpen: is not called, so it seems to me something fails over on Apple's side of things, just in between asking for the menu, and actually showing it.
Would anyone be able to shed a light on this?
Thanks in advance.
PS. For what it's worth, NSMenus I present manually (without relying on Apple's right-click handling) using popUpMenuPositioningItem:atLocation:inView: are shown.
Edit / Update / Clarification
The NSCollectionView in question is inside an NSWindow that's being shown when an NSStatusItem is clicked, like CoverSutra/TicToc/what have you. Some code from the MyWindow NSWindow subclass:
- (void)awakeFromNib {
[self setStyleMask:NSBorderlessWindowMask];
[self setExcludedFromWindowsMenu:YES];
}
- (BOOL)canBecomeMainWindow {
return YES;
}
- (BOOL)canBecomeKeyWindow {
return YES;
}
- (BOOL)isMovable {
return NO;
}
- (void)presentFromPoint:(NSPoint)point {
point.y -= self.frame.size.height;
point.x -= self.frame.size.width / 2;
[self setFrameOrigin:point];
[self makeMainWindow];
[self makeKeyAndOrderFront:self];
}
presentFromPoint: is the method I use to present it from any point I like, in my case from just below the NSStatusItem. (Not really relevant to this problem)
My application has LSUIElement in its Info.plist set to YES by the way, so it doesn't show a menu bar or a Dock icon. It lives in the status bar, and has a window that's shown when the NSStatusItem is clicked.
The view hierarchy is as follows:
MyWindow => contentView => NSScrollView => NSCollectionView
The NSCollectionView has an NSCollectionViewItem subclass connected to its itemPrototype property, and the NSCollectionViewItem subclass has an NSView subclass connected to its view property.
The NSView subclass, in turn, has an NSMenu connected to its menu property.
And last but not least: This NSMenu has one NSMenuItem sitting inside it.
Both the NSCollectionViewItem subclass and the NSView subclass do nothing interesting as of now, they're just empty subclasses.
The NSMenu connected to the NSView's menu property is what should be shown when the NSView is right-clicked, but as I hope I have made clear: It isn't actually shown.
Update
I still have no idea what caused this problem, but I've decided to 'move on' from NSCollectionView, as it wasn't really fit for what I was trying to do anyway, and I am now using TDListView which works like a charm.