MyViewController.m

/*
 Copyright (C) 2015 Apple Inc. All Rights Reserved.
  See LICENSE.txt for this sample’s licensing information
  
  Abstract:
  NSViewController subclass for managing the main app's content. 
 */
 
#import "MyViewController.h"
 
@interface MyViewController () <NSPopoverDelegate>
 
// configuration UI
@property (weak) IBOutlet NSMatrix *popoverType;
@property (weak) IBOutlet NSMatrix *popoverPosition;
@property (weak) IBOutlet NSButton *animatesCheckbox;
@property (weak) IBOutlet NSButton *useCustomDetachedWindow;
 
@property (strong) NSPopover *myPopover;
@property (strong) MyViewController *popoverViewController;
 
@property (strong) NSWindow *detachedWindow;
@property (strong) NSPanel *detachedHUDWindow;
 
@end
 
 
#pragma mark -
 
@implementation MyViewController
 
// -------------------------------------------------------------------------------
//  viewDidLoad
// -------------------------------------------------------------------------------
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // setup the default preferences
    self.animatesCheckbox.state = 1;                        // animates = YES
    self.useCustomDetachedWindow.state = 0;                 // use the built-in window support instead our own windows for detachable popovers
    
    [self.popoverType setState:1 atRow:0 column:0];         // type = normal
    [self.popoverPosition setState:1 atRow:1 column:0];     // position = NSMinYEdge
    
    _popoverViewController = [self.storyboard instantiateControllerWithIdentifier:@"PopoverViewController"];
    
    // To make a popover detachable to a separate window you need:
    // 1) a separate NSWindow instance
    //      - it must not be visible:
    //          (if created by Interface Builder: not "Visible at Launch")
    //          (if created in code: must not be ordered front)
    //      - must not be released when closed
    //      - ideally the same size as the view controller's view frame size
    //
    // 2) NSViewController instance for each window
    //
    // To make the popover detached, simply drag the visible popover away from its attached view
    //
    // For more detailed information, refer to NSPopover.h
    //
    NSRect frame = self.popoverViewController.view.bounds;
    NSUInteger styleMask = NSTitledWindowMask + NSClosableWindowMask;
    NSRect rect = [NSWindow contentRectForFrameRect:frame styleMask:styleMask];
    _detachedWindow = [[NSWindow alloc] initWithContentRect:rect styleMask:styleMask backing:NSBackingStoreBuffered defer:YES];
    self.detachedWindow.contentViewController = self.popoverViewController;
    self.detachedWindow.releasedWhenClosed = NO;
    
    styleMask = NSTitledWindowMask + NSClosableWindowMask + NSHUDWindowMask + NSUtilityWindowMask;
    _detachedHUDWindow = [[NSPanel alloc] initWithContentRect:rect styleMask:styleMask backing:NSBackingStoreBuffered defer:YES];
    self.detachedHUDWindow.contentViewController = self.popoverViewController;
    self.detachedHUDWindow.releasedWhenClosed = NO;
}
 
// -------------------------------------------------------------------------------
//  createPopover
// -------------------------------------------------------------------------------
- (void)createPopover
{
    if (self.myPopover == nil)
    {
        // create and setup our popover
        _myPopover = [[NSPopover alloc] init];
        
        // the popover retains us and we retain the popover,
        // we drop the popover whenever it is closed to avoid a cycle
        
        self.myPopover.contentViewController = self.popoverViewController;
        
        switch (self.popoverType.selectedRow)
        {
            case 0:
                self.myPopover.appearance = [NSAppearance appearanceNamed:NSAppearanceNameVibrantLight];
                break;
            case 1:
                self.myPopover.appearance = [NSAppearance appearanceNamed:NSAppearanceNameVibrantDark];
                break;
        }
        
        self.myPopover.animates = (self.animatesCheckbox).state;
        
        // AppKit will close the popover when the user interacts with a user interface element outside the popover.
        // note that interacting with menus or panels that become key only when needed will not cause a transient popover to close.
        self.myPopover.behavior = NSPopoverBehaviorTransient;
        
        // so we can be notified when the popover appears or closes
        self.myPopover.delegate = self;
    }
}
 
// -------------------------------------------------------------------------------
//  showPopoverAction:sender
// -------------------------------------------------------------------------------
- (IBAction)showPopoverAction:(id)sender
{
    switch (self.popoverType.selectedRow)
    {
        case 0:
            if (self.detachedHUDWindow.visible == YES)
            {
                [self.detachedHUDWindow close];
            }
            
            if (self.detachedWindow.visible)
            {
                // popover is already detached to a separate window, so select its window instead
                [self.detachedWindow makeKeyAndOrderFront:self];
                return;
            }
            break;
        case 1:
            if (self.detachedWindow.visible == YES)
            {
                [self.detachedWindow close];
            }
            
            if (self.detachedHUDWindow.visible)
            {
                // dark style popover is already detached to a separate window, so select its window instead
                [self.detachedHUDWindow makeKeyAndOrderFront:self];
                return;
            }
            break;
    }
    
    [self createPopover];
    
    NSButton *targetButton = (NSButton *)sender;
    
    // configure the preferred position of the popover
    NSRectEdge prefEdge = self.popoverPosition.selectedRow;
    
    [self.myPopover showRelativeToRect:targetButton.bounds ofView:sender preferredEdge:prefEdge];
}
 
 
#pragma mark - NSApplicationDelegate
 
// -------------------------------------------------------------------------------
//  applicationShouldTerminateAfterLastWindowClosed:sender
//
//  NSApplication delegate method placed here so the sample conveniently quits
//  after we close the window.
// -------------------------------------------------------------------------------
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
    return YES;
}
 
 
#pragma mark - NSPopoverDelegate
 
// -------------------------------------------------------------------------------
// Invoked on the delegate when the NSPopoverWillShowNotification notification is sent.
// This method will also be invoked on the popover.
// -------------------------------------------------------------------------------
- (void)popoverWillShow:(NSNotification *)notification
{
    NSPopover *popover = notification.object;
    if (popover != nil)
    {
        //... operate on that popover
    }
}
 
// -------------------------------------------------------------------------------
// Invoked on the delegate when the NSPopoverDidShowNotification notification is sent.
// This method will also be invoked on the popover.
// -------------------------------------------------------------------------------
- (void)popoverDidShow:(NSNotification *)notification
{
    // add new code here after the popover has been shown
}
 
// -------------------------------------------------------------------------------
// Invoked on the delegate when the NSPopoverWillCloseNotification notification is sent.
// This method will also be invoked on the popover.
// -------------------------------------------------------------------------------
- (void)popoverWillClose:(NSNotification *)notification
{
    NSString *closeReason = [notification.userInfo valueForKey:NSPopoverCloseReasonKey];
    if (closeReason)
    {
        // closeReason can be:
        //      NSPopoverCloseReasonStandard
        //      NSPopoverCloseReasonDetachToWindow
        //
        // add new code here if you want to respond "before" the popover closes
        //
    }
}
 
// -------------------------------------------------------------------------------
// Invoked on the delegate when the NSPopoverDidCloseNotification notification is sent.
// This method will also be invoked on the popover.
// -------------------------------------------------------------------------------
- (void)popoverDidClose:(NSNotification *)notification
{
    NSString *closeReason = [notification.userInfo valueForKey:NSPopoverCloseReasonKey];
    if (closeReason)
    {
        // closeReason can be:
        //      NSPopoverCloseReasonStandard
        //      NSPopoverCloseReasonDetachToWindow
        //
        // add new code here if you want to respond "after" the popover closes
        //
    }
    
    // release our popover since it closed
    _myPopover = nil;
}
 
// -------------------------------------------------------------------------------
// Invoked on the delegate to give permission to detach popover as a separate window.
// -------------------------------------------------------------------------------
- (BOOL)popoverShouldDetach:(NSPopover *)popover
{
    return YES;
}
 
// -------------------------------------------------------------------------------
// Invoked on the delegate to when the popover was detached.
// Note: Invoked only if AppKit provides the window for this popover.
// -------------------------------------------------------------------------------
- (void)popoverDidDetach:(NSPopover *)popover
{
    NSLog(@"popoverDidDetach");
}
 
// -------------------------------------------------------------------------------
// Invoked on the delegate asked for the detachable window for the popover.
// -------------------------------------------------------------------------------
- (NSWindow *)detachableWindowForPopover:(NSPopover *)popover
{
    NSWindow *window = nil;
 
    if (self.useCustomDetachedWindow.state)
    {
        window = self.detachedWindow;
        if ([popover.appearance.name isEqualToString:NSAppearanceNameVibrantDark])
        {
            // use the dark window (style HUD)
            window = self.detachedHUDWindow;
        }
    }
    
    return window;
}
 
@end