How to add custom NSView to Window - macos

I know how to do this in iOS but cannot figure it how to it in Cocoa.
I want to capture keyboard events and I think I need to override the acceptsFirstResponder method to accomplish that (keyDown method being triggered). So I created a class extending NSCustomView and tried to add it in the main Window but I just cannot understand how to do it. So far I added a Custom View to the main View then tried to add it programmatically like:
TestView *view = [[TestView alloc] init];
[[_window contentView] addSubview:view];
but this is not working. So how can I do this?

To see if the view has been added to a window, you can override the view's viewDidMoveToWindow method and log the value of [self window] to check (if it's nil then the view has been removed from a window):
- (void)viewDidMoveToWindow
{
NSLog(#"window=%p", [self window]);
[super viewDidMoveToWindow];
}
You should be subclassing NSView, not NSCustomView, and initWithFrame is the designated initializer for NSView, not init.
Try:
TestView *view = [[TestView alloc] initWithFrame:NSMakeRect(0, 0, 100, 200)];
[[_window contentView] addSubview:view];

Related

Can't set position of modal window

I have a window I want to display as a modal window (OS X 10.10). I'm loading the NIB for the window and am able to set the title successfully and then display the window. But whatever I do to try to affect the window position doesn't work.
This works (part of NSWindowController sub-class):
[[self window] setTitle:title];
[[NSApplication sharedApplication] runModalForWindow:[self window]];
Here are ways with which I've tried to affect the position after setting the title:
[[self window] setFrameOrigin: NSMakePoint(200.0, 200.0) ];
[[self window] setFrameTopLeftPoint: NSMakePoint(200.0, 200.0) ];
[[self window] setFrame: NSMakeRect(200, 300, [[self window] frame].size.width, [[self window] frame].size.height) display:YES];
(I've tried other values as well - just for testing, but nothing.)
I can even query the
[[self window] frame]
and it pretends to accept the new values, but the window stubbornly keeps showing up in the same position.
What gives?
I have solved this by
1- Make a new NSWindow subclass, overriding the center method, where you just make the frame of the new window positioned at whatever NSPoint you want:
class CenteredInParentWindow: NSWindow {
var parentMinX : CGFloat?
var parentMinY : CGFloat?
override func center() {
guard let parentMinX = parentMinX, let parentMinY = parentMinY else {
super.center()
return
}
self.setFrameOrigin(NSPoint(x: parentMinX, y: parentMinY))
}
}
2 - Set WindowController's window class to be the new NSWindow subclass, in Storyboard.
3- Instatiate the window controller and set the attributes of the subclassed window
let myWindowController = self.storyboard!.instantiateController(withIdentifier: "windowID") as! PlansWindowController
if let customWindow = myWindowController.window as? CenteredInParentWindow {
customWindow.parentMinX = NSApplication.shared.mainWindow?.frame.minX
customWindow.parentMinY = NSApplication.shared.mainWindow?.frame.minY
}
NSApp.runModal(for: myWindowController.window!)
}
You may need runModal method for making the window a modal one. Don't forget to include NSApp.stopModal() in the windowWillClose method which is available in NSWindowDelegate in your View controller

How to use Storyboard to make popover that can be used in code?

I'm building a collection of forms each of which contains several fields. Some of the fields are UITextFields that will display a date. I've created a new class called DatePickerTextField, a descendant of UITextField. When a DatePickerTextField is tapped I'd like for a UIDatePicker control to appear in a popover.
My question is how do I use the storyboard to implement the popover? I can do a segue when there is a specific, visible control in the scene. But how do I represent a generic popover in the scene that I can attach to any instantiated DatePickerTextField that becomes active?
You can create segue that is not connected to any control but I don't think that there would be way to specify anchor point for popover from code. Another option is to create ViewController that is not connected with any segue. When editing storyboard, create ViewController which will be placed in popover, select it and navigate to Utilities pane->Attributes Inspector. Set Size to Freeform, Status Bar to None, specify unique Identifier that will be used to instantiate ViewController from code. Now you can change the size of ViewController by selecting its View and navigating to Utilities pane->Size Inspector.
After that you can create popover from code:
- (IBAction)buttonPressed:(id)sender {
UIView *anchor = sender;
UIViewController *viewControllerForPopover =
[self.storyboard instantiateViewControllerWithIdentifier:#"yourIdentifier"];
popover = [[UIPopoverController alloc]
initWithContentViewController:viewControllerForPopover];
[popover presentPopoverFromRect:anchor.frame
inView:anchor.superview
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
One caveat is that you need to hold reference to popover as ivar of your class, otherwise it'll crash because UIPopoverController would be released and deallocated after buttonPressed returns:
#interface MyViewController : UIViewController {
// ...
UIPopoverController *popover;
// ...
}
So, I had a similar issue, and in case others might benefit, I figured I'd share it, since I benefit so much from stackoverflow.
This solution allows you to set the anchor of a customizable popover segue. It also allows you to configure the segue to be modal or not (I could not find a way to prevent the segue by dimming the exterior context, so if someone knows how to do that, I would be interested in hearing it); this is accomplished by setting the passthrough views for the popover controller. I also added the capacity to specify a custom view, rather than the view of the source viewcontroller (since I needed this capacity); this portion is not critical to the solution.
DynamicPopoverSegue.h
#interface DynamicPopoverSegue : UIStoryboardPopoverSegue
#property BOOL isModal;
#property UIView* sourceView;
#property CGRect anchor;
#end
DynamicPopoverSegue.m
#implementation DynamicPopoverSegue
- (void)perform
{
if (!self.popoverController.popoverVisible)
{
UIViewController* dst = (UIViewController*)self.destinationViewController;
UIViewController* src = (UIViewController*)self.sourceViewController;
UIView* inView = _sourceView ? _sourceView : src.view;
self.popoverController.contentViewController = dst;
if (!_isModal)
{
[self.popoverController setPassthroughViews:[[NSArray alloc] initWithObjects:inView, nil]];
}
[self.popoverController presentPopoverFromRect:_anchor
inView:inView
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
}
#end
Then you just set your segue to "Custom" in the storyboard, and set the segue class to "DynamicPopoverSegue". In my case, since I wanted to associate it with dynamic layers in a view, I could not set the anchor, so I created the segue by control clicking from the view controller icon in the bar beneath my view controller to the view controller I was using to present the popupover.
To call the popover segue:
[self performSegueWithIdentifier:#"MyPopoverSegue" sender:self];
And to configure the popover segue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"MyPopoverSegue"])
{
DynamicPopoverSegue* popoverSegue = (DynamicPopoverSegue*)segue;
// set the anchor to wherever you want it to be
popoverSegue.anchor = _destinationFrame;
}
}
- (IBAction)pressItemChooseOprateRoom:(id)sender {
if (isPad){
// UIView *anchor = sender;
UIViewController *viewControllerForPopover =
[self.storyboard instantiateViewControllerWithIdentifier:#"OperateRoomList"];
_myPopover = [[UIPopoverController alloc]
initWithContentViewController:viewControllerForPopover];
CGRect rc=[self getBarItemRc:sender];
[_myPopover presentPopoverFromRect:rc
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
[MLControl shared].popover =self;
// [self perfformSegueWithIdentifier:SEGUE_POP_OPERATEROOM sender:self];
}else{
[self iphoneOpenOperateRoomList];
/* [self performSegueWithIdentifier:#"iPhonePushOperateRoom" sender:self];
*/
}
}
-(void)iphoneOpenOperateRoomList{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"OperateRoomList"];
// if (!index.showTabBar) {
// vc.hidesBottomBarWhenPushed = YES;
// }
[self.navigationController pushViewController:vc animated:YES];
}
Just used the answer from Jonnywho for my SWIFT project. In case you need it:
Here's the SWIFT version:
let anchor: UIView = sender
var viewControllerForPopover = self.storyboard?.instantiateViewControllerWithIdentifier("GameAboutViewController") as! UIViewController?
let popover = UIPopoverController(contentViewController: viewControllerForPopover!)
popover.presentPopoverFromRect(anchor.frame, inView: anchor, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
Add a UIView in the scene dock.
You can add it as a subview to any existing view on the view controller.
You can then toggle it's isHidden property as you require.
You can add multiple such subviews and create many such popups.
This technique will save you from setting up a new View Controller and using segues.

loading a borderless window from a xib with view

I am trying to load a borderless window from a .xib in my program. I can load a borderless window with by overriding [[[NSWindow]] initWithContentRect:styleMask:backing:defer:] as follows:
- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag {
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:flag];
if (!self) {
return nil;
}
[self setOpaque:NO];
[self setHasShadow:YES];
[self setLevel:NSFloatingWindowLevel];
[self setBackgroundColor:[NSColor clearColor]];
[self setAlphaValue:1.0];
// Ignore events
[self setIgnoresMouseEvents:YES];
return self;
}
When another method which contains [self orderFront:self]; is called, a window shows. However, I have a separate .xib file with a window created that I want to show when this method is called. I have the file's owner set as NSApplication and the window itself is of the class that contains the aforementioned code. How do I, when I call the method with [self orderFront:self];, load the window in the xib and show it instead of this class creating a window?
If I understand what you're trying to do, you can use NSWindowController to load an NSWindow from a separate nib (or xib) file. Sub-class NSWindowController, and put your controller code in there. Create that object in the xib file and set that to be the file's owner. Link the NSWindow to the NSWindowController's delegate outlet.
Then it's as easy as:
NSWindowController * windowController = [[[YourWindowClass alloc] initWithWindowNibName:#"YourWindowClass"] autorelease];
NSWindow * sheet = [windowController window];

xcode viewDidLoad

I'm getting my feet wet with objective-C. And thought I'd play with iphone development.
I have a Page Controller.h and .m
Inside my implementation file I've thrown a UIButton inside the viewDidLoad method.
- (void)viewDidLoad
{
[super viewDidLoad];
// content that loads in view
UIButton *myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
myButton.frame = CGRectMake(10, 160, 300, 44);
[myButton setTitle:#"Press Me!" forState:UIControlStateNormal];
myButton.titleLabel.font = [UIFont systemFontOfSize:14];
// add to a view
[self.view addSubview:myButton];
}
So far it's showing fine. This makes me ask about the difference between loadView vs viewDidLoad.
Also, I want to run my own function when the button is pressed, but I constantly get an executable error from xcode.
Here's what I've interjected within the code above:
- (void)viewDidLoad
{
[super viewDidLoad];
// content that loads in view
UIButton *myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
myButton.frame = CGRectMake(10, 160, 300, 44);
[myButton setTitle:#"Press Me!" forState:UIControlStateNormal];
myButton.titleLabel.font = [UIFont systemFontOfSize:14];
//added this
[myButton addTarget:self action:#selector(runClick) forControlEvents:UIControlEventTouchUpInside];
// add to a view
[self.view addSubview:myButton];
}
-(void)runClick{
NSLog(#"does this work?");
}
Does anything seem out of place to anyone?
Also, In the PageController.h I added: -(void)runClick;
Here's the Error I catch:
2012-03-17 16:04:30.077 Blah[510:207] -[__NSCFString runClick]: unrecognized selector sent to instance 0x6a0b4f0
About the difference between loadView and viewDidLoad:
According to Apple, the controller (not you!) calls this method when it's view is requested and it's nil. So, it will create and load the view.
You can override loadView to create your own view (if you don't have a nib for that controller)
If you want to add something to the view created by a view controller with a nib file do it on viewDidLoad, and remove it on viewDidUnload.
About the error, can you provide more information? Are you debugging? What's the line it crashes?
Did you declare -(void)runClick in your .h file? (obvious question).
Also, if you want to get access to the control's properties (say if it were a slider, etc.), you need to add the sender bit:
-(void)runClick (id)sender
I believe you need to add a colon after runClick as in:
action:#selector(runClick:)

Access main window from a NSDocument class

I have a NSDocument class, where I'd need to access the main menu window, the one that gets opened when the app start. When I operate in that window from the app all seems to work, but when trying to do the same operations from readFromFileWrapper:ofType:error: the window I access seems to be nil. Why this happens?
EDIT: Some code which deals with this:
- (BOOL)readFromFileWrapper:(NSFileWrapper *)fileWrapper ofType:(NSString *)typeName error:(NSError **)outError
{
if([[NSFileManager alloc] fileExistsAtPath:[NSString stringWithFormat:#"%#/Project.plist",[[self fileURL] path]]]) {
NSLog(#"%#", [[self fileURL] path]);
NSDictionary *project = [NSDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:#"%#/Project.plist",[[self fileURL] path]]];
if([[project objectForKey:#"type"] isEqualToString:#"vote"]) {
[self openProject:[[self fileURL] path] type:#"vote"];
return YES;
} else if([[project objectForKey:#"type"] isEqualToString:#"quiz"]) {
[self openProject:[[self fileURL] path] type:#"quiz"];
return YES;
} else {
return NO;
}
} else {
return NO;
}
}
That is my readFromFileWrapper:ofType:error: method. Here is my openProject:type: method:
-(void)openProject:(NSString *)filepath type:(NSString *)type
{
NSLog(#"Opening project # %#",filepath);
NSLog(#"%#", [MainWindow description]);
[projectDesignerView setFrame:[[[[MainWindow contentView] subviews] objectAtIndex:0] frame]];
[projectDesignerToolbar setFrame:[MainWindow frame] display:FALSE];
[[MainWindow contentView] replaceSubview:[[[MainWindow contentView] subviews]objectAtIndex:0] with:projectDesignerView];
[[projectDesignerToolbar toolbar] setShowsBaselineSeparator:YES];
[MainWindow setToolbar:[projectDesignerToolbar toolbar]];
[MainWindow setRepresentedFilename:filepath];
[MainWindow setTitle:[NSString stringWithFormat:#"%# - %#", [[filepath lastPathComponent] stringByDeletingPathExtension], [projectDesignerToolbar title]]];
NSString *path = [[NSBundle mainBundle] pathForResource:#"projectDesigner" ofType:#"html"];
NSURL *url = [NSURL fileURLWithPath:path];
[[projectDesignerWebview mainFrame] loadRequest:[NSURLRequest requestWithURL:url]];
}
NSLog(#"%#", [MainWindow description]); returns nil, when MainWindow should be the Main App Window. I think the problem is that double-clicking on a file reallocs all, and hence is failing.
It's not entirely clear what you're asking. You mention that MainWindow is an outlet in MainMenu.xib but you don't specify what class is defining the outlet.
If this window is designed to have a single main "project" window then you should assign the outlet property in your application delegate.
You can then access this from other classes using something like [(YourAppDelegate*)[NSApp delegate] mainWindow];.
If, however, you are trying to obtain a reference to the window of the current document then it's a little bit more complicated.
The reason that NSDocument does not have a window outlet by default is that it is designed to work with instances of NSWindowController that themselves manage the various windows related to the document. This is so a document can have multiple windows showing different views of the same data, additional palettes related to the document and so on. Each instance of NSWindowController would have its own window nib file and window outlet.
By default, NSDocument creates a single instance of NSWindowController for you if you do not specifically create and assign NSWindowController instances to the document. This is automatic, you don't need to even know the window controller exists.
That means that if you aren't managing your document windows with NSWindowController instances yourself, you can get the window attached to the NSWindowController that is automatically-created by NSDocument like so:
/* Only implement this in an NSDocument instance where the
automatic window controller is being used.
If the document has multiple window controllers, you must
keep track of the main window controller yourself
and return its window
*/
- (NSWindow*)documentWindow
{
if([[self windowControllers] count] == 1)
{
return [[[self windowControllers] firstObject] window];
}
return nil;
}
The normal way to handle this is to add an IBOutlet to your NSDocument subclass, then hook it up to the document window in the .xib file.
In your .h:
#interface MyDocument : NSDocument
#property (nonatomic, assign) IBOutlet NSWindow *docWindow;
#end
In your .m:
#implementation MyDocument : NSDocument
#synthesize docWindow;
#end
Then, the most important part, open up MyDocument.xib (or whatever it's called), and drag a connection from File's Owner (assuming that's your NSDocument subclass, which it is by default) to the main document window, and hook it up to the docWindow outlet.

Resources