├── NSAlert+Popover.h ├── NSAlert+Popover.m └── README.md /NSAlert+Popover.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAlert+Popover.h 3 | // 4 | // Created by Raffael Hannemann on 30.04.13. 5 | // Copyright (c) 2013 Raffael Hannemann. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | typedef void (^NSAlertCompletionBlock)(NSInteger result); 11 | 12 | /** This category adds methods to run an NSAlert within a NSPopover below any kind of NSView. 13 | */ 14 | @interface NSAlert (Popover) 15 | 16 | /** This block will be executed with the index of the button, the user has clicked, as the parameter. 17 | */ 18 | @property (copy) NSAlertCompletionBlock completionBlock; 19 | 20 | /** The reference to the parent Popover, that contains this alert, is required to close the Popover from within the button click handling. 21 | */ 22 | @property (strong) NSPopover *parentPopover; 23 | 24 | /** The target view below which this alert will be popped over. The reference is required for enqueuing mumtuple Popover alerts. 25 | */ 26 | @property (weak) IBOutlet NSView *targetView; 27 | 28 | /** The main method of this category to open an NSAlert within a NSPopover below any kind of NSView. 29 | */ 30 | - (void) runAsPopoverForView:(NSView *)aView preferredEdge:(NSRectEdge)preferredEdge withCompletionBlock:(NSAlertCompletionBlock)aBlock; 31 | 32 | /** Convenient method that uses NSYMaxEdge as preferred edge. 33 | */ 34 | - (void) runAsPopoverForView:(NSView *)aView withCompletionBlock:(NSAlertCompletionBlock)aBlock; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /NSAlert+Popover.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAlert+MenuItemPopUp.m 3 | // Glow Foundation 4 | // 5 | // Created by Raffael Hannemann on 30.04.13. 6 | // Copyright (c) 2013 Raffael Hannemann. All rights reserved. 7 | // 8 | 9 | #import "NSAlert+Popover.h" 10 | #import 11 | 12 | @implementation NSAlert (Popover) 13 | 14 | static char COMPLETIONBLOCK_KEY; 15 | static char PARENTPOPOVER_KEY; 16 | static char TARGETVIEW_KEY; 17 | static NSMutableArray *previouslyOpenedAlerts; 18 | static NSPopover *currentlyOpenedPopover; 19 | static NSAlert *currentlyOpenedAlert; 20 | 21 | - (void) runAsPopoverForView:(NSView *) aView withCompletionBlock:(NSAlertCompletionBlock) aBlock { 22 | [self runAsPopoverForView:aView preferredEdge:NSMaxYEdge withCompletionBlock:aBlock]; 23 | } 24 | 25 | - (void) runAsPopoverForView:(NSView *) view preferredEdge:(NSRectEdge)preferredEdge withCompletionBlock:(NSAlertCompletionBlock)block { 26 | 27 | // Set this alert as the target of all its buttons 28 | for (NSButton *button in [self buttons]) { 29 | [button setTarget:self]; 30 | [button setAction:@selector(stopSynchronousPopoverAlert:)]; 31 | } 32 | 33 | // Store block and target view references for later usage 34 | self.completionBlock = block; 35 | self.targetView = view; 36 | 37 | // Force a layout update, which hides unused buttons 38 | [self layout]; 39 | 40 | if (!previouslyOpenedAlerts) { 41 | previouslyOpenedAlerts = [NSMutableArray array]; 42 | } 43 | 44 | // Instantiate a new NSPopover with a view controller that manages this alert's view 45 | NSViewController *controller = [[NSViewController alloc] init]; 46 | NSPopover *popover = [[NSPopover alloc] init]; 47 | [controller setView:[self.window contentView]]; 48 | [popover setContentViewController:controller]; 49 | 50 | // Store the reference to this alert's parent popover 51 | [self setParentPopover:popover]; 52 | 53 | // Enqueue the potentially currently opened alert 54 | if (currentlyOpenedPopover) { 55 | [previouslyOpenedAlerts addObject:currentlyOpenedAlert]; 56 | [currentlyOpenedPopover close]; 57 | } 58 | 59 | // Open the alert within the popover and mark it as the currently shown one. 60 | [popover showRelativeToRect:view.bounds ofView:view preferredEdge:NSMaxYEdge]; 61 | currentlyOpenedPopover = popover; 62 | currentlyOpenedAlert = self; 63 | } 64 | 65 | - (void) stopSynchronousPopoverAlert: (NSButton *) clickedButton { 66 | 67 | // Determine clicked button index 68 | NSUInteger clickedIx = [[self buttons] indexOfObject:clickedButton]; 69 | 70 | // And determine the return code of this button 71 | NSInteger returnCode = 0; 72 | switch (clickedIx) { 73 | case NSAlertFirstButtonReturn: 74 | case NSAlertSecondButtonReturn: 75 | case NSAlertThirdButtonReturn: 76 | returnCode = clickedIx; 77 | break; 78 | default: 79 | returnCode = NSAlertThirdButtonReturn +clickedIx -2; 80 | break; 81 | } 82 | 83 | // Execute the calback with the return code 84 | if (self.completionBlock) self.completionBlock(returnCode); 85 | 86 | // Close the popover and remove if from the queue 87 | NSPopover *parent = self.parentPopover; 88 | if (parent) [parent close]; 89 | currentlyOpenedAlert = nil; 90 | currentlyOpenedPopover = nil; 91 | 92 | // Check for previously shown Popover Alerts 93 | [self checkForPreviouslyShownAlerts]; 94 | } 95 | 96 | - (void) checkForPreviouslyShownAlerts { 97 | // If previously opened alerts are referenced, open the last one. 98 | if (previouslyOpenedAlerts && previouslyOpenedAlerts.count>0) { 99 | NSAlert *alert = [previouslyOpenedAlerts lastObject]; 100 | [previouslyOpenedAlerts removeObject:alert]; 101 | [alert runAsPopoverForView:alert.targetView withCompletionBlock:alert.completionBlock]; 102 | } 103 | } 104 | 105 | /** 106 | * The following methods are required to add properties to the category. 107 | */ 108 | 109 | // Property Completion Block 110 | - (void) setCompletionBlock: (NSAlertCompletionBlock) block{ 111 | objc_setAssociatedObject(self, &COMPLETIONBLOCK_KEY, block, OBJC_ASSOCIATION_COPY); 112 | } 113 | - (NSAlertCompletionBlock) completionBlock { 114 | return objc_getAssociatedObject(self, &COMPLETIONBLOCK_KEY); 115 | } 116 | 117 | // Property Parent Popover 118 | - (void) setParentPopover:(NSPopover *)parentPopover { 119 | objc_setAssociatedObject(self, &PARENTPOPOVER_KEY, parentPopover, OBJC_ASSOCIATION_RETAIN); 120 | } 121 | - (NSPopover *) parentPopover { 122 | return objc_getAssociatedObject(self, &PARENTPOPOVER_KEY); 123 | } 124 | 125 | // Property Target View 126 | - (void) setTargetView:(NSPopover *)targetView { 127 | objc_setAssociatedObject(self, &TARGETVIEW_KEY, targetView, OBJC_ASSOCIATION_RETAIN); 128 | } 129 | - (NSView *) targetView { 130 | return objc_getAssociatedObject(self, &TARGETVIEW_KEY); 131 | } 132 | 133 | @end 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NSAlert+Popover 2 | 3 | This category adds methods to open the alert within a NSPopover below any kind of NSView. The alerts do not run as modal windows, that is, your app continues execution while the alert's result will be processed in a execution block. 4 | 5 | ![NSAlert+Popover Preview](https://s3.amazonaws.com/cocoacontrols_production/uploads/control_image/image/715/original.jpg "NSAlert shown within a NSPopover using the NSAlert+Popover category") 6 | 7 | ## Usage 8 | 9 | Create a NSAlert and call ```runAsPopoverForView:withCompletionBlock:;```: 10 | 11 | NSAlert *alert = [NSAlert alertWithMessageText:@"Do you really want to delete this item?" 12 | defaultButton:@"Delete" 13 | alternateButton:@"Learn more" 14 | otherButton:@"Cancel" 15 | informativeTextWithFormat:@"Deleting this item will erase all associated data in the database. Click learn more if you need additional information."]; 16 | 17 | [alert runAsPopoverForView:self.showNextDateButton withCompletionBlock:^(NSInteger *result) { 18 | // handle result 19 | }]; 20 | 21 | ## Requirements 22 | 23 | NSPopovers require at least OS X 10.7 (OS X Lion). 24 | This category has been built for and tested **with ARC enabled** only! 25 | 26 | ## Contact 27 | 28 | * Raffael Hannemann 29 | * [@raffael_me](http://www.twitter.com/raffael_me/) 30 | * http://www.raffael.me/ 31 | 32 | ## License 33 | 34 | Copyright (c) 2013 Raffael Hannemann 35 | Under BSD License. 36 | 37 | ## Want more? 38 | 39 | Follow [@raffael_me](http://www.twitter.com/raffael_me/) for similar releases. --------------------------------------------------------------------------------