Mac: NSWindowController, view does not open after it's closed - objective-c

This is probably something simple that I am missing but this is what is happening:
I am making a Menu item app (application is agent = YES)
So I have a MainViewAppDelegate that kind of runs the entire thing. It gets a detection when a button on the menu item gets clicked.
Then I have an upload NSWindowController:
#import <Cocoa/Cocoa.h>
#interface UploadView : NSWindowController
- (IBAction)upload:(id)sender;
#end
.m:
-(id)init {
if (! (self = [super initWithWindowNibName:#"UploadView"])) {
// Initialization code here.
return nil;
}
return self;
}
In MainViewAppDelegate, when that button is pressed I do:
if (!uploadView) {
uploadView = [[UploadView alloc] init];
}
[uploadView showWindow:self];
Now, this works as long as I don't click out of xcode. Once I do that (say I make chrome the active tab) then it does not work anymore even if I go back to xcode.
Any thoughts?

I dont see any issue in the UploadView code. But can you try connecting the UploadView Panel in xib to File's Owner window outlet and try. I created a similar demo now and tried and i dont see any issue.

All I had to add was:
[NSApp activateIgnoringOtherApps:YES];
before trying to open the window.

Related

Can't make undo & redo to work in cocoa core data app

I have problem to implement undo and redo in my core data cocoa application. This is second question that I am asking on same topic, (you can read my first question here) and even that I get solid advice on my first question, I still can't make undo and redo to work.
Anyway...here is my current code:
Here is the relevant code from MainWindowController.h file:
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
#property (nonatomic,strong) NSManagedObjectContext *mObjContext;
#property AppDelegate *appDelegate;
Here is the code from the MainWindowController.m file:
#import "MainWindowController.h"
#interface MainWindowController () <NSWindowDelegate>
#end
#implementation MainWindowController
- (void)windowDidLoad {
self.appDelegate = (AppDelegate*)[[NSApplication sharedApplication] delegate];
self.mObjContext = self.appDelegate.managedObjectContext;
[[self window]setDelegate:self];
[self windowWillReturnUndoManager:self.window];
}
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window
{
return [[self document] undoManager];
}
#end
My window has the following Outlets in connection inspector: delegate: File's owner
Menu: MainMenu
Whenever I make changes in the records in my nstableviews, changes are recorded but undo and redo menu items remain gray. I placed break point in windowWillReturnUndoManager, but I can't see any undoManager returned there...Can anyone tell me what I am doing wrong?
You are returning the undoManager of the document (which is probably nil) instead of the undoManager of the managed object context. Change windowWillReturnUndoManager to
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window
{
return self.mObjContext.undoManager;
}
Well, the issue is finally solved. I add new edit menu item, while keeping the old one. When I make change in the record, the new menu item had undo item available and worked. Redo worked too.
So I delete the old edit menu item, and now everything works like a charm. Too bad that I didn't try this earlier, but....

How can I set a menu item click to respond to IBAction from another class?

I'm pretty new to XCode/Objective-C/Cocoa. I want to implement a settings window for my app.
I have a MainMenu.xib which also holds my main Window. From the menu, I want to open a settings window. I created Settings.xib and appropriate .h and .m files to hold what that window would do.
Settings.h:
#import <Cocoa/Cocoa.h>
#interface Settings : NSWindowController <NSApplicationDelegate>
-(IBAction)openSettings:(id)senderId;
#property (nonatomic, retain) Settings *thisWindow;
#end
Settings.m:
#import "Settings.h"
#implementation Settings
- (void)windowDidLoad {
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
// open preferences window
- (IBAction)openSettings:(id)senderId
{
_thisWindow = [[Settings alloc] initWithWindowNibName:#"Settings"];
[_thisWindow showWindow:self];
}
#end
I dragged my Preferences menu item to first responder, and selected openSettings: from there.
However, the item is still disabled and I'm pretty sure it's because I did nothing to link the Settings interface to my MainMenu.xib, which works with AppDelegate.h/m.
How do I make this work? All other solutions I found didn't quite work for me.
If I understand you clear you want to store your MainMenu and MainWindowController in a two separate classes.
Open your main menu nib-file. Remove window from the object tree.
Check in Project Settings -> General -> Main interface is still your MainMenu (without .xib-extension).
Create (implement) your custom MainWindowController class (with a nib-file may be).
Open AppDelegate class. In - (void)applicationDidFinishLaunching:(NSNotification *)aNotification method create an instance of main window controller class, show the window
Use this code below
MainWindowController *controller=[[MainWindowController alloc] initWithNibName:#"MainWindowController"];
[controller showWindow:nil];
[controller.window makeKeyAndOrderFront:nil];
Here it is.
Okay so in your mainwindowcontroller, declare a property of type NSWindowController *settingsWindow. Init it with the corresponding xib.
Then create a method called -(void)openSettings, with one line [self.settingsWindow showWindow:self];
Then also in your mainWindowController initialization, init a NSMenuItem, and set it's action to openSettings. Then add that NSMenuItem to the Mainmenu where you'd like programmatically, like this
//mainMenu is your application's menu-- if you switched index to 1 it would be the 'File' menu
NSMenu *mainMenu = [[[NSApp mainMenu] itemAtIndex:0] submenu];
[mainMenu insertItem:newItem atIndex:4];
I ended up using my AppDeleate.m to open the dialog instead. I linked the menu button to the AppDelegate object in the interface builder, and used openSettings:. Here's how it looks:
// open preferences window
- (IBAction)openSettings:(id)senderId
{
_settingsWindow = [[Settings alloc] initWithWindowNibName:#"Settings"];
[_settingsWindow showWindow:self];
}
In AppDelegate.m, instead of Settings.m.

NSPanel - Why can I not set my Panel title and data values before showing the window?

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.

How do i close instance of NSWindowController

I'm learning some obj-c and therefore i'm building a small cocoa application.
From the MainMenu.xib i have added a menu to the "Main menu" in the top. When click, this triggers a IBAction that opens an instance of a window, in this case a window for managing categories.
This category window has a NSWindowController, looks like this:
// CategoriesWindow.h
#import <Cocoa/Cocoa.h>
#interface CategoriesWindow : NSWindowController
-(IBAction)OpenCategoriesWindow:(id)sender;
#end
// CategoriesWindow.m
#import "CategoriesWindow.h"
#implementation CategoriesWindow
-(IBAction)OpenCategoriesWindow:(id)sender
{
CategoriesWindow *Categories = [[CategoriesWindow alloc] initWithWindowNibName:#"CategoriesWindow"];
[Categories showWindow:self];
}
#end
To this i have a CategoriesWindow.xib with a NSTableView that does some things, so there for i have a CategoryTableController.h and .m that handles the data for this table.
When i hit a button i want it to do a bunch of things, and then i want the window to close it self. That is, i want this window to close itself from a IBAction in the CategoryTableController.m.
How do I do this? One bad thing with this setup (followed from a tutorial somewhere...) is that I can open a lots of instances of this window by clicking the menu-button.
Any tips or ideas where to begin?
From there Reference:
[Categories close];
However there is something wrong with your implementation as you are creating an instance of the class from an instance method of the class. That doesn't look right to me. Also you aren't retaining the new instance anywhere, so it will be possibly destroyed under ARC or leaked under MRR.
I think you might want:
-(IBAction)OpenCategoriesWindow:(id)sender
{
[self showWindow:sender];
}
-(IBAction)CloseCategoriesWindow:(id)sender
{
[self close];
}
Although I can't be sure.

Window ordering and XIB

I am setting my first steps in OSX development and I've run into some problems. I have quite some experience with iOS development but the window system for OSX programs is something else.
I am making a client for a social network like twitter and need 2 seperate window controller for first starting the app, one if you are logged in to show your timeline and one for logging in, if you are not yet logged in. In the info.plist you need to give it a main.xib. For this I made an empty xib which I hide, the second the app starts. This is not really a good solutions IMO, what is a better solution for this? I want to keep the windows seperate from the appdelegate because that way I can keep my code seperated.
This gives me a problem, when I open my 'second' window to login it shows up but isn't active. I have tried all the things like, orderFront:, activateIgnoringOtherApps:, makeKeyAndOrderFront: & more. But this all doesn't work..
So: First off, is there a better way to handle the main.xib that is needed in the info.plist and if not, is there a way around the focus problem?
I'm working om osx 10.7
For more than one-offs, you really ought to separate your app delegate from your window controllers. Go ahead and create a new Cocoa application from the template. In MainMenu.xib, delete the window. In AppDelegate.h delete the IBOutlet to the NSWindow. Create a couple new subclasses of NSWindowController complete with XIBs--perhaps LoginWindowController and TimelineWindowController.
For "final" NSWindowController subclasses (i.e. those which won't be subclassed), the best practice for designated initializers is
//for our example class LoginWindowController
- (id)init
{
self = [super initWithWindowNibName:#"LoginWindowController"];
if (self) {
//....
}
return self;
}
Now in your app delegate, you should have #properties for the two different window controller instances:
//Within AppDelegate.m
#import "AppDelegate.h"
#import "LoginWindowController.h"
#import "TimelineWindowController.h"
#interface AppDelegate ()
#property (nonatomic) LoginWindowController *loginWindowController;
#property (nonatomic) TimelineWindowController *timelineWindowController;
//For the sake of this demo, add a property for the loggedIn state:
#property (nonatomic) BOOL loggedIn;
#end
You ought to have some sort of method in your app delegate that presents the correct window controller. Let's call it -updateWindowVisibility:
- (void)updateWindowVisibility
{
BOOL isLoggedIn = self.loggedIn;
BOOL loginWindowVisible = self.loginWindowController.window.isVisible;
BOOL showLoginWindow = !isLoggedIn;
BOOL timelineWindowVisible = self.timelineWindowController.window.isVisible;
BOOL showTimelineWindow = isLoggedIn;
if (!loginWindowVisible && showLoginWindow) {
if (!self.loginWindowController) self.loginWindowController = [[LoginWindowController alloc] init];
[self.loginWindowController showWindow:nil];
} else if (loginWindowVisible && !showLoginWindow) {
[self.loginWindowController close];
self.loginWindowController = nil;
}
if (!timelineWindowVisible && showTimelineWindow) {
if (!self.timelineWindowController) self.timelineWindowController = [[TimelineWindowController alloc] init];
[self.timelineWindowController showWindow:nil];
} else if (timelineWindowVisible && !showTimelineWindow) {
[self.timelineWindowController close];
self.timelineWindowController = nil;
}
}
This method as implemented above does a tiny bit more work than is necessary given the present setup, but should be easier to modify when you need to show/hide other windows. All that's left to do at this point is to call -updateWindowVisibility from -applicationDidFinishLaunching:.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.isLoggedIn = NO;
[self updateWindowVisibility];
}
I've posted an example app to github which demonstrates this approach.
In terms of structure (your first question), I would recommend this:
Create a XIB with one blank window and a big NSView (called, say, megaView) covering it. Create an IBOutlet in AppDelegate for your big NSView. Set the app to use this XIB on load.
Create two separate NSView XIBs: one for the state of being logged in, one for the state of being logged out. Put your layout in these.
Create two NSViewController subclasses: one controlling the logic of each NSView you just created. Let's call them LoggedOutViewController and LoggedInViewController.
Jump back to the two NSViews you created. Set the File Owner of your logged in NSView to LoggedInViewController and the File Owner of your logged out NSView to LoggedOutViewController. Hook up each File Owner's view (right-click on File Owner to find it) to the respective NSView.
In your app delegate, determine the user's authentication status in whatever way you need.
If logged in, do this:
NSViewController *loggedInController = [[NSViewController alloc] initWithNibName:#"NibNameGoesHere" bundle:nil];
[[self megaView] addSubview:[loggedInController view]];
Otherwise do the above process with your loggedOutController:
NSViewController *loggedOutController = [[NSViewController alloc] initWithNibName:#"OtherNibNameGoesHere" bundle:nil];
[[self megaView] addSubview:[loggedOutController view]];
That should get you what you want and will likely clear up your second question in the process. The difference between my answer and Nate's is that mine uses the same window. Instantiating view controllers conditionally and loading their views into superviews is probably the most important aspect of Cocoa I learned.