I'm trying to create a sheet that I'm loading from a custom nib file and has it's own Window Controller. In my app delegate upon a button press, I call
- (IBAction)loginLogout:(id)sender {
if (![self isLoggedIn]) {
// need to login
LoginManager *manager = [[LoginManager alloc] initWithWindowNibName:#"LoginSheet"];
[manager presentLoginWithWindow:self.window];
}
}
Then in the window controller (the LoginManager class), I have this
- (void)presentLoginWithWindow:(NSWindow *)window {
if (!self.window) {
[NSBundle loadNibNamed:#"LoginSheet" owner:self];
}
[NSApp beginSheet:self.window modalForWindow:window modalDelegate:self didEndSelector:#selector(didEndSheet:returnCode:contextInfo:) contextInfo:nil];
}
But I end up with this.
Perhaps you left the sheet window's "Visible At Launch" option checked in Interface Builder?
Related
I have a list of files. Next to it I have a QLPreviewView which shows the currently selected file.
Unfortunately QLPreviewView loads a web view to preview bookmark files. Some web pages can grab keyboard focus. E.g. the Gmail login form places the insertion point into the user name field.
This breaks the flow of my application. I want to navigate my list using arrow keys. This is disrupted when keyboard focus is taken away from the table view.
So far the best I could come up with is to override - [NSWindow makeFirstResponder:] and not call super for instances of classes named with a QL prefix. Yuck.
Is there a more reasonable way to
Prevent unwanted changes of first responder?
or prevent user interaction on QLPreviewView and its subviews?
I ended up using a NSWindow subclass that allows QLPreviewViews and its private subviews to become first responder on user interaction, but prevents these views from simply stealing focus.
- (BOOL)makeFirstResponder:(NSResponder *)aResponder
{
NSString *classname = NSStringFromClass([aResponder class]);
// This is a hack to prevent Quick Look from stealing first responder
if ([classname hasPrefix:#"QL"]) {
BOOL shouldMakeFirstRespnder = NO;
NSEvent *currentEvent = [[NSApplication sharedApplication] currentEvent] ;
NSEventType eventType = currentEvent.type;
if ((eventType == NSLeftMouseDown) || (eventType == NSRightMouseDown) || (eventType == NSMouseEntered)) {
if ([aResponder isKindOfClass:[NSView class]]) {
NSView *view = (NSView *)aResponder;
NSPoint locationInWindow = currentEvent.locationInWindow;
NSPoint locationInView = [view convertPoint:locationInWindow fromView:nil];
BOOL pointInRect = NSPointInRect(locationInView, [view bounds]);
shouldMakeFirstRespnder = pointInRect;
}
}
if (!shouldMakeFirstRespnder) {
return NO;
}
}
return [super makeFirstResponder:aResponder];
}
Maybe you can subclass QLPreviewView and override its becomeFirstResponder so that you can either enable or disable it when your application should allow it to accept focus.
Header
#interface MyQLPreviewView : QLPreviewView
#end
Implementation
#implementation
- (BOOL)becomeFirstResponder
{
return NO;
}
#end
I am showing a sheet within my main window. I present the sheet using this code:
AddContactWindowController *addContact = [[AddContactWindowController alloc] initWithWindowNibName:#"AddContactWindow"];
addContact.currentViewController = myView;
self.addWindowController = addContact;
[self.view.window beginSheet: addContact.window completionHandler:^(NSModalResponse returnCode) {
NSLog(#"completionHandler called");
}];
AddContactWindowController is a NSWindowController subclass. It has a view controller within it. Inside the view is a "close" button which invokes this:
[[[self view] window] close];
This does close the window, but the completionHandler from beginSheet is not invoked. This causes me problems down the road.
Is there any particular way we should close the NSWindow sheet for the completion handler to be successfully called? I've also tried [[[self view] window] orderOut:self] but that doesn't work either.
Thanks.
You will want to call -endSheet:returnCode: on your window, rather than just ordering it out.
You must properly finish the modal session.
I used to call - (void)performClose:(id)sender and stop the modal session in the delegate method.
- (void)windowWillClose:(NSNotification *)notification {
[NSApp stopModal];
}
But for a sheet, endSheet looks more appropriate.
self.addWindowController = addContact;
[self.view.window beginSheet:self.addWindowController.window];
...
...
[self.view.window endSheet:self.addWindowController.window];
self.addWindowController = nil
I used this source http://www.cats.rwth-aachen.de/library/programming/cocoa
to create my custom sheet.
I created a NSPanel object in existing .xib file and connected with IBOutlet
My source code:
.h
#interface MainDreamer : NSWindow <NSWindowDelegate>
{
...
NSPanel *newPanel;
}
...
#property (assign) IBOutlet NSPanel *newPanel;
.m
#dynamic newPanel;
...
//this method is wired with button on main window and calls a sheet
- (IBAction)callPanel:(id)sender
{
[NSApp beginSheet:newPanel
modalForWindow:[self window] modalDelegate:self
didEndSelector:#selector(myPanelDidEnd:returnCode:contextInfo:)
contextInfo: nil]; //(__bridge void *)[NSNumber numberWithFloat: 0]
}
//this method is wired with cancel and ok buttons on the panel
- (IBAction)endWorkPanel:(id)sender
{
[newPanel orderOut:self];
[NSApp endSheet:newPanel returnCode:([sender tag] == 9) ? NSOKButton : NSCancelButton];
}
//closing a sheet
- (void)myPanelDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
if (returnCode == NSCancelButton) return;
else{
return;
}
}
So callPanel works fine, sheet appears but I can't interact with controls on the sheet (with buttons). They don't react on a click (even visually).
Where the problem lies?
Heh, I forgot about
[newDreamPanel close];
in applicationDidFinishLaunching method. I wrote it because I wanted the panel to not appear when main window launches.
In fact, the Visible At Launch panel's property should be activated in IB. The close method works too, but side effect is that all controls become unable on the panel.
i am developing a small application. On the first window, i have an option to create a new account. I use a button "Continue" for this. When this button is clicked, another window for creating a new account opens. I want that once this window is opened, no other instance of this nib file should load again. Even if the user clicks on "Continue" again, already opened instance of the nib file (the one for creating a new account) should come to front.
Is there any API which will help to check if one instance of nib is already loaded?
Or may be something that gives a list of all the nibs loaded in the memory?
Thanks in Advance...
UPDATE:
#interface WelcomePageController : NSObject {
IBOutlet NSTextField * userNameField;
IBOutlet NSPopUpButton * actionList;
IBOutlet NSWindow * welcomePage;
CreateNewAccountWindowController * createNewAccountWindowController;
}
-(IBAction) changePasswordButton:(id)sender;
-(IBAction) logOutButton:(id)sender;
-(IBAction) continueButton:(id)sender;
#end
#implementation WelcomePageController
-(void)windowDidUpdate:(id)sender{
UserInfo * user=[UserInfo uInfoObject];
[userNameField setStringValue:[user.firstName stringByAppendingFormat:#" %#!", user.lastName]];
if ([user.userType isEqual:#"Standard"]) {
[actionList setAutoenablesItems:NO];
[[actionList itemAtIndex:2]setEnabled:NO];
[[actionList itemAtIndex:3]setEnabled:NO];
}
else {
[actionList setAutoenablesItems:YES];
}
}
-(IBAction) changePasswordButton:(id)sender{
[NSBundle loadNibNamed:#"ChangePassword" owner:self];
}
-(IBAction) continueButton:(id)sender{
if ([actionList indexOfSelectedItem]==0) {
[NSBundle loadNibNamed:#"ViewAvailableItemsWindow" owner:self];
}
else if([actionList indexOfSelectedItem]==1){
[NSBundle loadNibNamed:#"NewOrderPage" owner:self];
}
else if([actionList indexOfSelectedItem]==2){
[NSBundle loadNibNamed:#"ManageItemList" owner:self];
}
else {
if(!createNewAccountWindowController){
createNewAccountWindowController=[[CreateNewAccountWindowController alloc]init];
}
[createNewAccountWindowController showWindow:self];
//[NSBundle loadNibNamed:#"NewAccount" owner:self];
}
}
-(IBAction) logOutButton:(id)sender{
[NSBundle loadNibNamed:#"LoginPage" owner:self];
[[sender window]close];
}
#end
This is the complete code that i am using....The code in question is the method continueButton..The else condition(last one)..
I have tried this. I open the NewAccountWindow once i click on the Continue button. I close the window and click on the continue button again. However this time the "NewAccountWindow" does not open again(even the already existing instance does not show up).
The standard approach for this is to have a subclass of NSWindowController (potentially holding outlets to the window widgets) responsible for loading the nib file. For instance,
#interface CreateAccountWindowController : NSWindowController {
// …
}
// …
#end
#implementation CreateAccountWindowController
- (id)init {
self = [super initWithWindowNibName:#"CreateAccount"];
return self;
}
// …
#end
When the user clicks the Continue button, you have an action method that handles that click. In the class that contains the action method, declare an instance variable for the corresponding window controller:
CreateAccountWindowController *createAccountWindowController;
and, in the action method that handles clicks of the Continue button, create an instance of CreateAccountWindowController if and only if none exists yet. This will make sure at most one instance of that window controller exists at any given time, hence the corresponding nib file is loaded at most once:
- (IBAction)showCreateAccountWindow:(id)sender {
if (! createAccountWindowController) {
createAccountWindowController = [[CreateAccountWindowController alloc] init];
}
[createAccountWindowController showWindow:self];
}
I'm geting this strange behavior. I'm using a panel with text to show to the user when the app is waiting for some info. This panel is show modally to prevent the user to click something.
When the loading panel is hidden all the items on the toolbar are disabled and the validateToolbarItem method is not called.
I'm showing the panel in this way:
- (void)showInWindow:(NSWindow *)mainWindow {
sheetWindow = [self window];
[self sheetWillShow];
[NSApp beginSheet:sheetWindow modalForWindow:mainWindow modalDelegate:nil didEndSelector:nil contextInfo:nil];
[NSApp runModalForWindow:sheetWindow];
[NSApp endSheet:sheetWindow];
[sheetWindow orderOut:self];
}
- (void)dismissModal {
[sheetWindow close];
[NSApp stopModal];
}
How can I force the toolbar to validate in this case?
Edit after comment:
I have already tried:
[[[NSApp mainWindow] toolbar] validateVisibleItems]
[[NSApp mainWindow] update];
[NSApp updateWindows];
[NSApp setWindowsNeedUpdate:YES];
All after call dismissModal. I'm thinking that the problem is elsewhere....
The problem is that NSToolbar only sends validation messages to NSToolbarItem's that are of Image type, which none of mine were. In order to validate any or all NSToolbarItems's, create a custom subclass of NSToolBar and override the validateVisibleItems: method. This will send validation messages to ALL visible NSToolbarItem's. The only real difference is that instead of having the Toolbar class enable or disable the item with the returned BOOL, you need to enable or disable the item in the validation method itself.
#interface CustomToolbar : NSToolbar
#end
#implementation CustomToolbar
-(void)validateVisibleItems
{
for (NSToolbarItem *toolbarItem in self.visibleItems)
{
NSResponder *responder = toolbarItem.view;
while ((responder = [responder nextResponder]))
{
if ([responder respondsToSelector:toolbarItem.action])
{
[responder performSelector:#selector(validateToolbarItem:) withObject:toolbarItem];
}
}
}
}
#end
Now, assume you have a controller with an IBAction method that handles Actions for a NSSegmentedControl in your toolbar:
- (IBAction)backButton:(NSSegmentedControl*)sender
{
NSInteger segment = sender.selectedSegment;
if (segment == 0)
{
// Action for first button segment
}
else if (segment == 1)
{
// Action for second button segment
}
}
Place the following in the same controller that handles the toolbar item's Action:
-(BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem
{
SEL theAction = [toolbarItem action];
if (theAction == #selector(backButton:))
{
[toolbarItem setEnabled:YES];
NSSegmentedControl *backToolbarButton = (NSSegmentedControl *)toolbarItem.view;
[backToolbarButton setEnabled:YES forSegment:0];
[backToolbarButton setEnabled:NO forSegment:1];
}
return NO;
}
The result is that you have complete control over which segments are enabled or disabled.
This technique should be applicable to almost any other type of NSToolbarItem as long as the item's Received Action is being handled by a controller in the responder chain.
I hope this helps.
NSToolbar *toolbar; //Get this somewhere. If you have the window it is in, call [window toolbar];
[toolbar validateVisibleItems];