How to display sheet view in NSWindow - macos

How do I implement the view in following image.
The view which appears when + button is clicked in System Preferences > Network
I have following questions:
Does this view system has a specific name (like popover), because I have seen it in many places in Mac.
How to implement it in IB ?
Can this be done in a popover window instead of NSWindow ?(or is it only possible in NSWindow like toolbar)
Update:
Updating the title for better visibility

In Cocoa these are called sheets. Take a look at the sheet programming guide, however, this is terribly out of date!
You need to call -beginSheet:completionHandler: on the window you want to display the sheet. If you have single-window application you can ask the AppDelegate for the window and launch the sheet like so,
// This code should be in AppDelegate which implement the -window method
NSWindow *targetWindow = [self window]; // the window to which you want to attach the sheet
NSWindow *sheetWindow = self.sheetWindowController.window // the window you want to display at a sheet
// Now start-up the sheet
[targetWindow beginSheet:sheetWindow completionHandler:^(NSModalResponse returnCode) {
switch (returnCode) {
case NSModalResponseCancel:
NSLog(#"%#", #"NSModalResponseCancel");
break;
case NSModalResponseOK:
NSLog(#"%#", #"NSModalResponseOK");
break;
default:
break;
}
}];
You will notice that when the sheet completes it will return a certain modal response --- we will return to this point in a shortly.
Next you need to implement the content that you want to display in the sheet; this must be done in an NSWindow. I find it much easier to use a NSWindowController and implement the window in a separate XIB file. For example, see below,
Now you need to implement the code in your custom NSWindowController (or plain NSWindow if you are old-school and love to manage your own NIB loading) which will issue the correct modal response. Here I have hooked up the cancel and OK buttons to the following actions methods,
- (IBAction)cancelButtonAction:(id)sender {
[[[self window] sheetParent] endSheet:self.window returnCode:NSModalResponseCancel];
}
- (IBAction)OKButtonAction:(id)sender {
[[[self window] sheetParent] endSheet:self.window returnCode:NSModalResponseOK];
}
The model response will get sent to your completion handler block.
Sample project on github.

Related

OS X storyboards: using "show" segue without allowing duplicate new windows to show?

Right now I have an OS X storyboard app that has a main window, and a button on it that triggers a "show" segue for another view controller. Right now I've got the segue set up as modal because if I don't, the user could click the same button again and end up with two copies of the same window.
Is there a way for me to accomplish this without having to restructure the storyboard to embed these view controllers in a separate window controller (which would seem to defeat the purpose of the flexibility the storyboards offer)?
Edit: While the answer below does work, it is definitely not the best way. In your storyboard, select the view controller for the destination view then go to the attributes inspector and change Presentation from Multiple to Single. That's it, no code required.
Not sure this is the best way but in the NSViewController that is pushing the segue, you could add a property for the destination NSViewController and, in your prepareForSegue:sender: method, assign the destination view controller. Finally, in the shouldPerformSegueWithIdentifier:sender: method, check to see if the destination view controller is assigned, and, if so, bring its window to the front and return NO meaning don't perform the segue, otherwise, return YES. Here's a quick example (to be included in the NSViewController with the button to initiate the segue):
#interface ViewController ()
#property (weak) NSViewController *pushedViewController;
#end
#implementation ViewController
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if (self.pushedViewController) {
[self.pushedViewController.view.window makeKeyAndOrderFront:self];
return NO;
}
return YES;
}
- (void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
self.pushedViewController = segue.destinationController;
}
#end
When you close the window containing the destination view controller, this will set the pushedViewController property of the original view controller to nil so the segue will perform if the window is not already opened. Again, there may be a better way to do this. Hope this helps.
Jon

Sheets are not displaying properly on OSX

I am very much new to OSX development. Consider this as my first app. I want to display a sheet when a button is clicked on the main window. I am using Nib
Following is my code for .h file
#import <Foundation/Foundation.h>
#import "WebKit/Webkit.h"
#interface MainViewObject : NSObject
- (IBAction)accountButtonPressed:(id)sender;
- (IBAction)cancelSheetButtonPressed:(id)sender;
.m file as follows
#import "MainViewObject.h"
#implementation MainViewObject
- (IBAction)accountButtonPressed:(id)sender {
[NSApp beginSheet:self.accountSheet
modalForWindow:[mainWindowView window]
modalDelegate:nil
didEndSelector:nil
contextInfo:nil];
[NSApp runModalForWindow:self.accountSheet];
[NSApp endSheet:self.accountSheet];
[self.accountSheet orderOut:self];
}
- (IBAction)cancelSheetButtonPressed:(id)sender {
// Return to normal event handling
[NSApp endSheet:self.accountSheet];
// Hide the sheet
[self.accountSheet orderOut:sender];
}
When I run the app I get something like this:
http://i.imgur.com/DzJJ6.png
I am stuck and I have no idea what wrong in this. I am not able to get the sheet and the not able to even close the app. I have referred to some examples on internet.
- (IBAction)accountButtonPressed:(id)sender {
[NSApp beginSheet:self.accountSheet
modalForWindow:[mainWindowView window]
modalDelegate:nil
didEndSelector:nil
contextInfo:nil];
[NSApp runModalForWindow:self.accountSheet];
[NSApp endSheet:self.accountSheet];
[self.accountSheet orderOut:self];
}
Wow, looking at that, it's no surprise the screenshot looks as it is.
Let's walk through that one line at a time. When you click the Accounts button, you're doing 4 things immediately in succession:
You're telling the application to begin showing the sheet attached to your main window. This is OK, and is actually the only code you want in that accountButtonPressed: method.
Right after beginning that sheet, you tell the application you want to also show that sheet all by itself (not attached to any windows but right in the middle of the screen), in an application-modal fashion, which blocks all other events from being processed in the application. In other words, this line doesn't really make sense. You either show a window as a sheet in a "document-modal" fashion (which only ties up the window that the sheet is attached to) or in an "application-modal" fashion, but not both at the same time. ;-)
Immediately after having just shown the sheet, you tell NSApp to stop showing the sheet. Now, you do want to do this eventually, but dismissing the sheet 0.0005 seconds after having just shown it will likely leave your users a little frustrated.
You now tell the sheet to hide itself. This needs to be done from your didEndSelector: method, which brings us to the problems in your first method.
-
[NSApp beginSheet:self.accountSheet
modalForWindow:[mainWindowView window]
modalDelegate:nil
didEndSelector:nil
contextInfo:nil];
This is good but read the documentation for beginSheet:modalForWindow:modalDelegate:didEndSelector:contextInfo: and also the Sheet Programming Topics: Using Custom Sheets. (The Companion guides links at the top of Class Reference pages are especially helpful for learning how to use the APIs in the real world. They were extremely helpful when I was learning).
Specifying nil for the modalDelegate: means you don't have anything that's waiting to be notified about when the sheet has stopped being shown (this happens when you call [NSApp endSheet:sheet]). You also haven't specified the #selector you want called when the sheet is ended. A selector is kind of like a function, aka a "method".
Your code should look something like this:
#implementation MDAppDelegate
- (IBAction)showSheet:(id)sender {
[NSApp beginSheet:self.sheet
modalForWindow:self.window
modalDelegate:self
didEndSelector:#selector(sheetDidEnd:returnCode:contextInfo:)
contextInfo:NULL];
}
- (IBAction)cancel:(id)sender {
[NSApp endSheet:self.sheet];
}
- (IBAction)ok:(id)sender {
[NSApp endSheet:self.sheet];
}
- (void)sheetDidEnd:(NSWindow *)sheet
returnCode:(NSInteger)returnCode
contextInfo:(void *)contextInfo {
[sheet orderOut:nil];
}
#end
In this example, you click the Show Sheet button, and the sheet starts being shown attached to the main window. In the sheet, there is a Cancel and an OK button, which both call their respective methods. In each of these methods, you call [NSApp endSheet:self.sheet]. This tells NSApp that it should then call the sheetDidEnd:returnCode:contextInfo: method on the object specified to be the modal delegate. In sheetDidEnd:returnCode:contextInfo: you then tell the sheet to hide itself.
EDIT:
Every NSWindow has a "Visible at launch" flag that can be set in Interface Builder. If this flag is set, the window will be visible at the time the nib file is loaded. If it isn't set, the window is hidden until you programmatically show it. Just edit the flag in the nib file like shown:

Hide/Unhide UINavigationbar when the screen is tapped

I'm very new with iOS Development and I have just created one of my first apps, in my .xib file I have a UINavigationBar that I want to hide/show when a part of the screen is tapped by the user (like in the Photo app). I've found some snippets online but I don't know where and how to use those.
I'd appreciate a lot if somebody could give me detailed informations about how to do this.
Add this toggle method anywhere in your UIViewController. This hides on first tap and shows again in second tap.
- (void)toggleNavBar:(UITapGestureRecognizer *)gesture {
BOOL barsHidden = self.navigationController.navigationBar.hidden;
[self.navigationController setNavigationBarHidden:!barsHidden animated:YES];
}
If there is no navigation controller, link the navigation bar with an IBOutlet and replace with
- (void)toggleNavBar:(UITapGestureRecognizer *)gesture {
BOOL barsHidden = self.navBar.hidden;
self.navBar.hidden = !barsHidden;
}
Then add the following in the method -(void)viewDidLoad {}
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(toggleNavBar:)];
[self.view addGestureRecognizer:gesture];
[gesture release];
If the view where you are going to tap is a UIWebViewController, you have to add the protocol to the view controller and set it as delegate gesture.delegate = self; then add the following:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
This is needed because the UIWebViewController already implements its own gesture recognizers.
Ultimately, you want to send the -setHidden: message to your navigation bar. The easiest way to do this is to make an Outlet and an Action in your in your view controller. Then, in your .xib file, connect the navigation bar to the outlet and some button (even a large, full screen one) to the action.
Outlets and Actions are basic techniques used over and over in iOS
(and Mac) programming, so if you don't understand them, best go read
up on them now. Every beginning iOS/Mac programming book covers this
topic as does Apple's own Getting Started guide (pay particular
attention to the Configuring the View section).
Inside your action, send a message to the outlet like so:
-(void)myButtonAction:(id)sender{
[[self myNavigationBarOutlet] setHidden:YES];
}
This will hide the navigation bar whenever your button is tapped.
(This assumes you have a UINavigationBar in your .xib like you say. These directions will be different if you're working with a UINavigationController that manages its own UINavigationBar)

resignFirstResponder not hiding keyboard on textFieldShouldReturn

I have a view with a UITextField which should hide the keyboard when return is pressed.
My function is this:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if ( textField == userPassword ) {
[textField resignFirstResponder];
}
return YES;
}
Normally the keyboard should be hidden but it stays on the screen. resignFirstResponder is correctly called. What am I missing?
I see you have the iPad tag on this. Do you happen to be presenting a modal view using UIModalPresentationFormSheet? If so, it looks like this is a limitation of the FormSheet modal presentation (either Apple is doing it intentionally for some reason, or it is a bug). See these other questions for more details:
Modal Dialog Does Not Dismiss Keyboard
Modal View Controller with keyboard on landscape iPad changes location when dismissed
There is this helpful method which allows you to dismiss the keyboard when presenting the Modal Dialog:
- (BOOL)disablesAutomaticKeyboardDismissal { return NO; }
This will override the default behavior of the modal dialog set by Apple and allow you dismiss the keyboard. It is in the UIViewController Class.
I hope this helps someone!
If you are using the Interface Builder, look if your UITextField has the delegated linked with your class.
-Select your UITextField and in your Connections look if exits one connection in Outlets->delegate. If not, conect with you File's Owner Class.
This need to be linked with your File's Owner Class. This delegate tell where to search for a method. If your are overriding a method, you need to tell where the object will search for that.
This solution worked for me after none of the above did. after calling resignFirstResponder i added a modal view & removed it.
[myTextField resignFirstResponder];
UIViewController *dummyController = [[UIViewController alloc] init];
UIView *dummy = [[UIView alloc] initWithFrame:CGRectMake(-1, -1,1,1)];
[dummyController setView:dummy];
[self presentModalViewController:dummyController animated:NO];
[dummyController dismissModalViewControllerAnimated:NO];
To deal with the bug mentioned by Brandon, you can try closing and re-opening your modal view controller as long as you still have a reference to it.
[textField resignFirstResponder];
[self dismissModalViewControllerAnimated:NO];
[self presentModalViewController:yourModalViewControllerReference animated:NO];
(where "self" should be the controller you used to originally open the modal view controller)
I was having the same problem. I realized that after connecting the delegate to the File's Owner in Interface Builder, I neglected to save in Interface Builder. Once I saved, I recompiled and the keyboard disappears correctly when hitting return.
xcode 4.5.1
Simply click control then on the textfield drag and release on the .h file
(control key+ drag)
then in the pop up menu select
connection=acton;
name= any name;
type=id;
event=did end on exit;
arguments=sender;
then click connect button
Did you remember to implement the UITextFieldDelegate protocol?
I have read so many articles about this issue, where the onscreen keyboard refuses to hide when you call resignFirstResponder, but none of the suggestions worked for me.
I'm using XCode 5 (iOS 7) and have a iPhone screen containing a couple of controls which require the onscreen keyboard, but if the user clicks on the UIButton, then I want the keyboard to disappear.
I probably wasted one full day experimenting with resignFirstResponder and adding disablesAutomaticKeyboardDismissal functions to return NO, but nothing worked. Once the onscreen keyboard appeared, I could never get it to disappear again.
But then I had a small brainwave (as I only have a small brain).
Now, when the user clicks on my UIButton, I simply disable the UITextField and UITextView controls.
- (IBAction)btnDate_Tapped:(id)sender {
// The user has clicked on the "Date" button.
self.tbClientName.enabled = NO;
self.tbComments.editable = NO;
And suddenly, the app finds it has no editable text fields needing an onscreen keyboard, and it neatly slides the keyboard out of sight.
(Relieved sigh.)
My UIButton actually makes a popup dialog appear. When the user dismisses the popup, I re-enable these two controls, so if the user taps in one of them, the keyboard will appear again.
-(void)popoverControllerDidDismissPopover:(UIPopoverController *) popoverController {
// The user has closed our popup dialog.
// We need to make our UITextField and UITextView editable again.
self.tbClientName.enabled = YES;
self.tbComments.editable = YES;
... etc...
}
Simple, isn't it !
And surprisingly, this workaround even works on UIViewControllers which appear in Modal style.
I hope this helps other XCode victims out there.
Based on your comment that it looks like focus has shifted, then I think what may be happening is that the keyboard is staying open for the next text input field. If your return key is a "Next" key, then returning YES for textFieldShouldReturn: will make the next textField the first responder, and keep the keyboard visible.
The easiest way is:
Go to your user interface builder,
select UITextField and "Control-Drag" to "Detail View Controller-Detail" and release.
The window will pop-up. Then under "Outlets" select "Delegate".
That's it. It worked for me.
if you are in UIModalPresentationFormSheet just call
- (BOOL)disablesAutomaticKeyboardDismissal
{
return NO;
}
Swift 3.0:
override var disablesAutomaticKeyboardDismissal: Bool {
get{
return false
}
set {
self.disablesAutomaticKeyboardDismissal = false
}
}
Swift 3.0
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
if textField == addressTextField {
textField.resignFirstResponder()
return false
}
return true
}

Make OSX application respond to first mouse click when not focused

Normal OSX applications eat the first mouse click when not focused to first focus the application. Then future clicks are processed by the application. iTunes play/pause button and Finder behave differently, the first click is acted on even when not focused. I am looking for a way to force an existing application (Remote Desktop Connection.app) to act on the first click and not just focus.
Check NSView's acceptsFirstMouse, it may be what you're looking for.
acceptsFirstMouse:
Overridden by subclasses to return YES if the receiver should be sent a mouseDown: message for an initial mouse-down event, NO if not.
(BOOL)acceptsFirstMouse:(NSEvent *)theEvent
Parameters
theEvent
The initial mouse-down event, which must be over the receiver in its window.
Discussion
The receiver can either return a value unconditionally or use the location of theEvent to determine whether or not it wants the event. The default implementation ignores theEvent and returns NO.
Override this method in a subclass to allow instances to respond to click-through. This allows the user to click on a view in an inactive window, activating the view with one click, instead of clicking first to make the window active and then clicking the view. Most view objects refuse a click-through attempt, so the event simply activates the window. Many control objects, however, such as instances of NSButton and NSSlider, do accept them, so the user can immediately manipulate the control without having to release the mouse button.
Responding to the first mouse click when not focused is called 'click through'. And its worth is debated heatedly, for instance here and here.
// Assuming you have 1 view controller that's always hanging around. Over ride the loadview. N.B. this won't work pre-yosemite.
- (void)loadView {
NSLog(#"loadView");
self.view = [[NSView alloc] initWithFrame:
[[app.window contentView] frame]];
[self.view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways);
trackingArea0 = [[NSTrackingArea alloc] initWithRect:self.view.bounds
options:opts
owner:self
userInfo:nil];
[self.view addTrackingArea:trackingArea0];
}
- (void)mouseEntered:(NSEvent *)theEvent {
NSLog(#"entered");
if ([[NSApplication sharedApplication] respondsToSelector:#selector(activateIgnoringOtherApps:)]) {
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
}
}

Resources