├── .gitignore ├── NSColor ├── NSColor+Hex.h └── NSColor+Hex.m ├── NSSplitView ├── NSSplitView+Animation.h └── NSSplitView+Animation.m ├── NSString ├── NSString+Size.h └── NSString+Size.m ├── NSObject ├── NSObject+VariableArgumentPerformSelector.h └── NSObject+VariableArgumentPerformSelector.m ├── NSPopover ├── NSPopover+Message.h └── NSPopover+Message.m └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store -------------------------------------------------------------------------------- /NSColor/NSColor+Hex.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSColor+Hex.h 3 | // 4 | // Created by Michael Robinson on 4/12/11. 5 | // License: http://pagesofinterest.net/license/ 6 | // 7 | 8 | #import 9 | 10 | @interface NSColor (Hex) 11 | 12 | + (NSColor *) colorWithHex:(NSString *)hexColor; 13 | 14 | - (NSString *) hexColor; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /NSSplitView/NSSplitView+Animation.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSSplitView+NSSplitView_Animation.h 3 | // 4 | // Created by Michael Robinson on 8/05/12. 5 | // License: http://pagesofinterest.net/license/ 6 | // 7 | // Adapted from: http://cocoadev.com/wiki/AnimatedNSSplitView 8 | // 9 | 10 | #import 11 | 12 | @interface NSSplitView (Animation) 13 | 14 | -(void) animateView:(int)viewIndex toDimension:(CGFloat)dimension; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /NSString/NSString+Size.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+ContainerSize.h 3 | // 4 | // Created by Michael Robinson on 6/03/12. 5 | // License: http://pagesofinterest.net/license/ 6 | // 7 | // Based on the Stack Overflow answer: http://stackoverflow.com/a/1993376/187954 8 | // 9 | 10 | #import 11 | 12 | @interface NSString (Size) 13 | 14 | 15 | - (NSSize) sizeWithWidth:(float)width andFont:(NSFont *)font; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /NSObject/NSObject+VariableArgumentPerformSelector.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+PerformSelectorVaried.h 3 | // Conductor 4 | // 5 | // Created by Michael Robinson on 23/05/12. 6 | // License: http://pagesofinterest.net/license/oftware. 7 | // 8 | 9 | #import 10 | 11 | @interface NSObject (VariableArgumentPerformSelector) 12 | 13 | - (void) performSelector:(SEL)aSelector withObjects:(NSObject *)firstObject, ... NS_REQUIRES_NIL_TERMINATION; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /NSString/NSString+Size.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+ContainerSize.m 3 | // 4 | // Created by Michael Robinson on 6/03/12. 5 | // License: http://pagesofinterest.net/license/ 6 | // 7 | // Based on the Stack Overflow answer: http://stackoverflow.com/a/1993376/187954 8 | // 9 | 10 | #import "NSString+Size.h" 11 | 12 | @implementation NSString (Size) 13 | 14 | - (NSSize) sizeWithWidth:(float)width andFont:(NSFont *)font { 15 | 16 | NSSize size = NSMakeSize(width, FLT_MAX); 17 | 18 | NSTextStorage *textStorage = [[[NSTextStorage alloc] initWithString:self] retain]; 19 | NSTextContainer *textContainer = [[[NSTextContainer alloc] initWithContainerSize:size] retain]; 20 | NSLayoutManager *layoutManager = [[[NSLayoutManager alloc] init] retain]; 21 | [layoutManager addTextContainer:textContainer]; 22 | [textStorage addLayoutManager:layoutManager]; 23 | [textStorage addAttribute:NSFontAttributeName value:font 24 | range:NSMakeRange(0, [textStorage length])]; 25 | [textContainer setLineFragmentPadding:0.0]; 26 | 27 | [layoutManager glyphRangeForTextContainer:textContainer]; 28 | 29 | size.height = [layoutManager usedRectForTextContainer:textContainer].size.height; 30 | 31 | [layoutManager release]; 32 | [textContainer release]; 33 | [textStorage release]; 34 | 35 | return size; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /NSPopover/NSPopover+Message.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSPopover+Message.h 3 | // Requires NSString+Size, available at: https://github.com/faceleg/Cocoa-Categories 4 | // 5 | // Created by Michael Robinson on 8/03/12. 6 | // License: http://pagesofinterest.net/license/ 7 | // 8 | 9 | #import 10 | 11 | @interface NSPopover (Message) 12 | 13 | + (void) showRelativeToRect:(NSRect)rect 14 | ofView:(NSView *)view 15 | preferredEdge:(NSRectEdge)edge 16 | string:(NSString *)string 17 | maxWidth:(float)width; 18 | 19 | + (void) showRelativeToRect:(NSRect)rect 20 | ofView:(NSView *)view 21 | preferredEdge:(NSRectEdge)edge 22 | string:(NSString *)string 23 | backgroundColor:(NSColor *)backgroundColor 24 | maxWidth:(float)width; 25 | 26 | + (void) showRelativeToRect:(NSRect)rect 27 | ofView:(NSView *)view 28 | preferredEdge:(NSRectEdge)edge 29 | string:(NSString *)string 30 | backgroundColor:(NSColor *)backgroundColor 31 | foregroundColor:(NSColor *)foregroundColor 32 | font:(NSFont *)font 33 | maxWidth:(float)width; 34 | 35 | + (void) showRelativeToRect:(NSRect)rect 36 | ofView:(NSView *)view 37 | preferredEdge:(NSRectEdge)edge 38 | attributedString:(NSAttributedString *)attributedString 39 | backgroundColor:(NSColor *)backgroundColor 40 | maxWidth:(float)width; 41 | @end 42 | -------------------------------------------------------------------------------- /NSObject/NSObject+VariableArgumentPerformSelector.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+PerformSelectorVaried.m 3 | // Conductor 4 | // 5 | // Created by Michael Robinson on 23/05/12. 6 | // 7 | 8 | #import "NSObject+VariableArgumentPerformSelector.h" 9 | #import "objc/message.h" 10 | 11 | #define MAX_MESSAGE_ARGUMENTS (10) 12 | 13 | @implementation NSObject (VariableArgumentPerformSelector) 14 | 15 | /** 16 | * Perform a selector with a variable number of arguments 17 | * @param aSelector The selector to perform 18 | * @param firstObject the first argument 19 | * @param ... Subsequent NSObject arguments 20 | */ 21 | - (void) performSelector:(SEL)aSelector withObjects:(NSObject *)firstObject, ... { 22 | 23 | // Prepare array of object pointers to be passed to objc_msgSend 24 | typedef NSObject *objectArray[MAX_MESSAGE_ARGUMENTS]; 25 | objectArray messageArguments = {0}; 26 | 27 | // Add the variadic NSObject arguments to the object pointer array 28 | size_t variadicArgumentIndex = 0; 29 | va_list variadicArguments; 30 | va_start(variadicArguments, firstObject); 31 | for (NSObject *variadicArgument = firstObject; variadicArgument != nil; variadicArgument = va_arg(variadicArguments, NSObject*)) { 32 | messageArguments[variadicArgumentIndex++] = variadicArgument; 33 | } 34 | va_end(variadicArguments); 35 | 36 | // Send the message 37 | objc_msgSend(self, aSelector, 38 | messageArguments[0], 39 | messageArguments[1], 40 | messageArguments[2], 41 | messageArguments[3], 42 | messageArguments[4], 43 | messageArguments[5], 44 | messageArguments[6], 45 | messageArguments[7], 46 | messageArguments[8], 47 | messageArguments[9]); 48 | 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /NSSplitView/NSSplitView+Animation.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSSplitView+NSSplitView_Animation.m 3 | // 4 | // Created by Michael Robinson on 8/05/12. 5 | // License: http://pagesofinterest.net/license/ 6 | // 7 | // Adapted from: http://cocoadev.com/wiki/AnimatedNSSplitView 8 | // 9 | 10 | #import "NSSplitView+Animation.h" 11 | 12 | @implementation NSSplitView (Animation) 13 | 14 | /** 15 | * Animate the split view panels such that the view at viewIndex has the width or height dimension. 16 | * Note that animating a panel to zero width or height will cause it to 'disappear', and such a panel will not animate again. Animating to no less than 1 pixel wide or high is sufficient to make a panel appear hidden. 17 | * @param viewIndex The index of the view to animate to dimension wide or high 18 | * @param dimension The width or height, depending on whether the NSSplitView is horizontally or vertically split, to animate to 19 | */ 20 | - (void) animateView:(int)viewIndex toDimension:(CGFloat)dimension { 21 | 22 | NSView *targetView = [[self subviews] objectAtIndex:viewIndex]; 23 | NSRect endFrame = [targetView frame]; 24 | 25 | if (![self isVertical]) { 26 | endFrame.size.height = dimension; 27 | } else { 28 | endFrame.size.width = dimension; 29 | } 30 | 31 | NSDictionary *windowResize; 32 | 33 | windowResize = [NSDictionary dictionaryWithObjectsAndKeys: targetView, NSViewAnimationTargetKey, 34 | [NSValue valueWithRect: endFrame], NSViewAnimationEndFrameKey, nil]; 35 | 36 | NSViewAnimation *animation = [[NSViewAnimation alloc] 37 | initWithViewAnimations:[NSArray arrayWithObject:windowResize]]; 38 | 39 | 40 | [animation setAnimationBlockingMode:NSAnimationBlocking]; 41 | [animation setDuration:0.5]; 42 | [animation startAnimation]; 43 | [animation release]; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /NSColor/NSColor+Hex.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSColor+Hex.m 3 | // 4 | // Created by Michael Robinson on 4/12/11. 5 | // License: http://pagesofinterest.net/license/ 6 | // 7 | 8 | #import "NSColor+Hex.h" 9 | 10 | @implementation NSColor (Hex) 11 | 12 | 13 | + (NSColor *) colorWithHex:(NSString *)hexColor { 14 | 15 | // Remove the hash if it exists 16 | hexColor = [hexColor stringByReplacingOccurrencesOfString:@"#" withString:@""]; 17 | int length = (int)[hexColor length]; 18 | bool triple = (length == 3); 19 | 20 | NSMutableArray *rgb = [[NSMutableArray alloc] init]; 21 | 22 | // Make sure the string is three or six characters long 23 | if (triple || length == 6) { 24 | 25 | CFIndex i = 0; 26 | UniChar character = 0; 27 | NSString *segment = @""; 28 | CFStringInlineBuffer buffer; 29 | CFStringInitInlineBuffer((CFStringRef)hexColor, &buffer, CFRangeMake(0, length)); 30 | 31 | 32 | while ((character = CFStringGetCharacterFromInlineBuffer(&buffer, i)) != 0 ) { 33 | if (triple) segment = [segment stringByAppendingFormat:@"%c%c", character, character]; 34 | else segment = [segment stringByAppendingFormat:@"%c", character]; 35 | 36 | if ((int)[segment length] == 2) { 37 | NSScanner *scanner = [[NSScanner alloc] initWithString:segment]; 38 | 39 | unsigned number; 40 | 41 | while([scanner scanHexInt:&number]){ 42 | [rgb addObject:[NSNumber numberWithFloat:(float)(number / (float)255)]]; 43 | } 44 | segment = @""; 45 | } 46 | 47 | i++; 48 | } 49 | 50 | // Pad the array out (for cases where we're given invalid input) 51 | while ([rgb count] != 3) [rgb addObject:[NSNumber numberWithFloat:0.0]]; 52 | 53 | return [NSColor colorWithCalibratedRed:[[rgb objectAtIndex:0] floatValue] 54 | green:[[rgb objectAtIndex:1] floatValue] 55 | blue:[[rgb objectAtIndex:2] floatValue] 56 | alpha:1]; 57 | } 58 | else { 59 | NSException* invalidHexException = [NSException exceptionWithName:@"InvalidHexException" 60 | reason:@"Hex color not three or six characters excluding hash" 61 | userInfo:nil]; 62 | @throw invalidHexException; 63 | 64 | } 65 | 66 | } 67 | 68 | - (NSString *) hexColor { 69 | 70 | // Convert colour to RGBA 71 | NSColor *rgb = [self colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; 72 | 73 | return [NSString stringWithFormat:@"#%0.2X%0.2X%0.2X", 74 | (int)([rgb redComponent] * 255), 75 | (int)([rgb greenComponent] * 255), 76 | (int)([rgb blueComponent] * 255)]; 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cocoa Categories 2 | A collection of Cocoa categories I've found useful. 3 | 4 | For the most recent license, see [Cocoa Categories License](http://pagesofinterest.net/license/). 5 | 6 | ## NSString+Size 7 | Adds `- (NSSize) sizeWithWidth:(float)width andFont:(NSFont *)font;` method to NSStrings. Returns the width and height of a box that would contain the rendered string with the given width & font. 8 | 9 | ## NSColor+Hex 10 | Adds the following: 11 | 12 | ```ObjectiveC 13 | + (NSColor *) colorWithHex:(NSString *)hexColor; 14 | 15 | - (NSString *) hexColor; 16 | ``` 17 | 18 | The class method `+ (NSColor *) colorWithHex:(NSString *)hexColor` attempts to initialise a new NSColor object from the given hexadecimal string. It will accept a three or six character string (# is optional). Anything else and it will raise an exception. 19 | 20 | The instance method `- (NSString *) hexColor;` returns a hexadecimal string representation of the given NSColor. If the colour's `colorSpaceName` is not one of: `NSCalibratedWhiteColorSpace`, `NSCalibratedBlackColorSpace`, `NSCalibratedRGBColorSpace` or `NSDeviceRGBColorSpace`, the method will return `@"transparent"`. 21 | 22 | ## NSPopover+Message 23 | Adds the class method: 24 | 25 | ```ObjectiveC 26 | + (void) showRelativeToRect:(NSRect)rect 27 | ofView:(NSView *)view 28 | preferredEdge:(NSRectEdge)edge 29 | string:(NSString *)string 30 | maxWidth:(float)width; 31 | ``` 32 | 33 | Intended to allow one to show a NSPopover containing a message with the minimum of fuss. 34 | 35 | Example: 36 | 37 | ```ObjectiveC 38 | [NSPopover showRelativeToRect:[view bounds] 39 | ofView:view 40 | preferredEdge:NSMaxXEdge 41 | string:@"Your message - the NSPopover will be as tall as required depending on your given maxWidth" 42 | maxWidth:200.0]; 43 | ``` 44 | 45 | More info: 46 | 47 | - [Resizing NSTextField to Fit Content](http://pagesofinterest.net/blog/2012/03/resizing-nstextfield-to-fit-content/ "Resizing NSTextField to Fit Content") 48 | - [User Feedback & the NSPopover](http://12412.org/2012/03/user-feedback-simplifying-the-nspopover/ "User Feedback & the NSPopover") 49 | 50 | ## NSSplitView+Animation 51 | 52 | Adds `- (void) animateView:(int)viewIndex toDimension:(CGFloat)dimension` method to NSSplitViews. Animates the split view panels such that the view at `viewIndex` has the pixel width or height of `dimension`. Note that animating a panel to zero width or height will cause it to 'disappear', and such a panel will not animate again. Animating a panel to no less than 1 pixel wide or high is sufficient to make the panel appear hidden. 53 | 54 | 55 | ## NSObject+VariableArgumentPerformSelector 56 | 57 | Adds `- (void) performSelector:(SEL)aSelector withObjects:(NSObject *)firstObject, ...` to NSObjects, which allows one to more conveniently perform selectors that require more than two arguments. 58 | 59 | ```ObjectiveC 60 | [object performSelector:@selector(selectorRequiringFourArguments:first:second:third:fourth) 61 | withObjects:first, second, third, fourth, nil]; 62 | ``` 63 | -------------------------------------------------------------------------------- /NSPopover/NSPopover+Message.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSPopover+Instance.m 3 | // Requires NSString+Size, available at: https://github.com/faceleg/Cocoa-Categories 4 | // 5 | // Created by Michael Robinson on 8/03/12. 6 | // License: http://pagesofinterest.net/license/ 7 | // 8 | 9 | #import "NSPopover+Message.h" 10 | #import "NSString+Size.h" 11 | 12 | @interface COICOPopoverView : NSView { 13 | NSColor *backgroundColour; 14 | } 15 | 16 | @property (nonatomic, assign) NSColor *backgroundColour; 17 | 18 | @end 19 | 20 | 21 | @implementation COICOPopoverView 22 | 23 | @synthesize backgroundColour; 24 | 25 | - (void)drawRect:(NSRect)aRect { 26 | if (self.backgroundColour == nil) { 27 | [self setBackgroundColour:[NSColor controlBackgroundColor]]; 28 | } 29 | 30 | NSGradient *gradient = [[NSGradient alloc] initWithStartingColor:backgroundColour 31 | endingColor:[NSColor controlBackgroundColor]]; 32 | 33 | NSRect drawingRect = [self frame]; 34 | drawingRect.origin.x = 0; 35 | drawingRect.origin.y = 0; 36 | 37 | NSBezierPath *border = [NSBezierPath bezierPathWithRoundedRect:drawingRect 38 | xRadius:5.0 39 | yRadius:5.0]; 40 | [gradient drawInBezierPath:border angle:270.0]; 41 | 42 | [super drawRect:aRect]; 43 | } 44 | 45 | @end 46 | 47 | @implementation NSPopover (Message) 48 | 49 | + (void) showRelativeToRect:(NSRect)rect 50 | ofView:(NSView *)view 51 | preferredEdge:(NSRectEdge)edge 52 | string:(NSString *)string 53 | maxWidth:(float)width { 54 | 55 | [NSPopover showRelativeToRect:rect 56 | ofView:view 57 | preferredEdge:edge 58 | string:string 59 | backgroundColor:[NSColor controlBackgroundColor] 60 | maxWidth:width]; 61 | } 62 | 63 | + (void) showRelativeToRect:(NSRect)rect 64 | ofView:(NSView *)view 65 | preferredEdge:(NSRectEdge)edge 66 | string:(NSString *)string 67 | backgroundColor:(NSColor *)backgroundColor 68 | maxWidth:(float)width { 69 | 70 | [NSPopover showRelativeToRect:rect 71 | ofView:view 72 | preferredEdge:edge 73 | string:string 74 | backgroundColor:backgroundColor 75 | foregroundColor:[NSColor controlTextColor] 76 | font:[NSFont systemFontOfSize:[NSFont systemFontSize]] 77 | maxWidth:width]; 78 | } 79 | 80 | + (void) showRelativeToRect:(NSRect)rect 81 | ofView:(NSView *)view 82 | preferredEdge:(NSRectEdge)edge 83 | string:(NSString *)string 84 | backgroundColor:(NSColor *)backgroundColor 85 | foregroundColor:(NSColor *)foregroundColor 86 | font:(NSFont *)font 87 | maxWidth:(float)width { 88 | 89 | NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string 90 | attributes:@{ 91 | NSFontAttributeName: font, 92 | NSForegroundColorAttributeName: foregroundColor }]; 93 | 94 | [NSPopover showRelativeToRect:rect 95 | ofView:view 96 | preferredEdge:edge 97 | attributedString:attributedString 98 | backgroundColor:backgroundColor 99 | maxWidth:width]; 100 | } 101 | 102 | + (void) showRelativeToRect:(NSRect)rect 103 | ofView:(NSView *)view 104 | preferredEdge:(NSRectEdge)edge 105 | attributedString:(NSAttributedString *)attributedString 106 | backgroundColor:(NSColor *)backgroundColor 107 | maxWidth:(float)width { 108 | 109 | float padding = 15; 110 | 111 | NSRect containerRect = [attributedString boundingRectWithSize:NSMakeSize(width, 0) 112 | options:NSStringDrawingUsesLineFragmentOrigin]; 113 | containerRect.size.width = containerRect.size.width *= (25/(containerRect.size.width+2)+1); 114 | 115 | NSSize size = containerRect.size; 116 | NSSize popoverSize = NSMakeSize(containerRect.size.width + (padding * 2), containerRect.size.height + (padding * 2)); 117 | 118 | containerRect = NSMakeRect(0, 0, popoverSize.width, popoverSize.height); 119 | 120 | NSTextField *label = [[[NSTextField alloc] initWithFrame:NSMakeRect(padding, padding, size.width, size.height)] retain]; 121 | 122 | [label setBezeled:NO]; 123 | [label setDrawsBackground:NO]; 124 | [label setEditable:NO]; 125 | [label setSelectable:NO]; 126 | [label setAttributedStringValue:attributedString]; 127 | [[label cell] setLineBreakMode:NSLineBreakByWordWrapping]; 128 | 129 | COICOPopoverView *container = [[[COICOPopoverView alloc] initWithFrame:containerRect] retain]; 130 | [container setBackgroundColour:backgroundColor]; 131 | [container addSubview:label]; 132 | [label setBounds:NSMakeRect(padding, padding, size.width, size.height)]; 133 | [container awakeFromNib]; 134 | 135 | NSViewController *controller = [[[NSViewController alloc] init] retain]; 136 | [controller setView:container]; 137 | 138 | NSPopover *popover = [[[NSPopover alloc] init] retain]; 139 | [popover setContentSize:popoverSize]; 140 | [popover setContentViewController:controller]; 141 | [popover setAnimates:YES]; 142 | [popover setBehavior:NSPopoverBehaviorTransient]; 143 | [popover showRelativeToRect:rect 144 | ofView:view 145 | preferredEdge:edge]; 146 | [popover release]; 147 | [controller release]; 148 | [container release]; 149 | [label release]; 150 | } 151 | 152 | 153 | @end 154 | --------------------------------------------------------------------------------