Extracting hex value from NSColor

Example project available on GitHub –

NSColor+Hex category available on GitHub –

This category on NSColor allows one to get or set an NSColor’s colour using hex values. Intitialising an NSColor with a hexadecimal colour:
1
2
3
4
5
6
7
8
@try {
    // Both shorthand and full forms of hexadecimal colours are accepted
    [colorWell setColor:[NSColor colorWithHex:@"#F00"]];
    [colorWell setColor:[NSColor colorWithHex:@"#Ff0000"]];
}
@catch (NSException *exception) {
    NSLog(@"%@", [exception description]);
}
It’ll throw an exception if one attempts to use a string that is not 3 or 6 characters in length, excluding the hash. Getting the hexadecimal representation of a given NSColor:
1
NSString *hexColor = [color hexColor]
This being my first Cocoa category, I’m sure it must have rough edges. Nevertheless, I have found it very useful. Below is the header for NSColor+Hex:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//
//  NSColor+Hex.h
//  CINSColor+Hex
//
//  Created by Michael Robinson on 4/12/11.
//  Copyright 2011 Code of Interest. All rights reserved.
//
 
#import <AppKit/AppKit.h>
 
@interface NSColor (Hex)
 
+ (NSColor *) colorWithHex:(NSString *)hexColor;
- (NSString *) hexColor;
 
@end
And the implementation, more verbose than I’d like but it does the job. If anyone improves on it, I’ll be happy to update this page & accept your pull request on GitHub:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
//
//  NSColor+Hex.m
//  CINSColor+Hex
//
//  Created by Michael Robinson on 4/12/11.
//  Copyright 2011 Code of Interest. All rights reserved.
//
 
#import "NSColor+Hex.h"
 
@implementation NSColor (Hex)
 
 
+ (NSColor *) colorWithHex:(NSString *)hexColor {
 
    // Remove the hash if it exists
    hexColor = [hexColor stringByReplacingOccurrencesOfString:@"#" withString:@""];
    int length = (int)[hexColor length];
    bool triple = (length == 3);
 
    NSMutableArray *rgb = [[NSMutableArray alloc] init];
 
    // Make sure the string is three or six characters long
    if (triple || length == 6) {
 
        CFIndex i = 0;
        UniChar character = 0;
        NSString *segment = @"";
        CFStringInlineBuffer buffer;
        CFStringInitInlineBuffer((CFStringRef)hexColor, &buffer, CFRangeMake(0, length));
 
 
        while ((character = CFStringGetCharacterFromInlineBuffer(&buffer, i)) != 0 ) {
            if (triple) segment = [segment stringByAppendingFormat:@"%c%c", character, character];
            else segment = [segment stringByAppendingFormat:@"%c", character];
 
            if ((int)[segment length] == 2) {
                NSScanner *scanner = [[NSScanner alloc] initWithString:segment];
 
                unsigned number;
 
                while([scanner scanHexInt:&number]){
                    [rgb addObject:[NSNumber numberWithFloat:(float)(number / (float)255)]];
                }
                segment = @"";
            }
 
            i++;
        }
 
        // Pad the array out (for cases where we're given invalid input)
        while ([rgb count] != 3) [rgb addObject:[NSNumber numberWithFloat:0.0]];
 
        return [NSColor colorWithCalibratedRed:[[rgb objectAtIndex:0] floatValue] 
                                         green:[[rgb objectAtIndex:1] floatValue] 
                                          blue:[[rgb objectAtIndex:2] floatValue] 
                                         alpha:1];
    }
    else {
        NSException* invalidHexException = [NSException exceptionWithName:@"InvalidHexException"
                                                                   reason:@"Hex color not three or six characters excluding hash"                                    
                                                                 userInfo:nil];
        @throw invalidHexException;
 
    }
 
}
 
- (NSString *) hexColor {
     if ([[self colorSpaceName] isEqualToString:NSCalibratedWhiteColorSpace]) {
        return [NSString stringWithFormat:@"#%0.2X%0.2X%0.2X",
            (int)(r * [self whiteComponent]),
            (int)(g * [self whiteComponent]),
            (int)(b * [self whiteComponent])];
    }
    else if ([[self colorSpaceName] isEqualToString:NSCalibratedBlackColorSpace]) {
        return [NSString stringWithFormat:@"#%0.2X%0.2X%0.2X",
            (int)(r * [self blackComponent]),
            (int)(g * [self blackComponent]),
            (int)(b * [self blackComponent])];
    }
    else if ([[self colorSpaceName] isEqualToString:NSCalibratedRGBColorSpace]
             || [[self colorSpaceName] isEqualToString:NSDeviceRGBColorSpace]) {
        return [NSString stringWithFormat:@"#%0.2X%0.2X%0.2X",
            (int)(r * [self redComponent]),
            (int)(g * [self blueComponent]),
            (int)(b * [self greenComponent])];
    }
    return @"transparent";
}
 
@end
Comments (2) | Trackback

Cocoa Warning: Implicitly declaring C library function ‘objc_getClass’

In Slider 2, I needed to check for the presence for Sparkle, and only load Slider 2′s included version when Sparkle isn’t found.

The code I used to do this is included below, but the point of this article is really to provide a solution to the following warning I encountered:

warning: Semantic Issue: Implicitly declaring C library function 'objc_getClass' with type 'id (const char *)'

One will get this warning if using code when checking for the existence of a class like:

if (!objc_getClass("SUUpdater")) { ... }

The solution is to use NSClassFromString instead:

if (!NSClassFromString(@"SUUpdater")) { ... }

For an in-depth tutorial on how to check for the presence of a framework within a Cocoa bundle, see Detecting Presence of a Framework Within an NSBundle (Plugin).

Comments (4) | Trackback

Sliding Modal Dialogs in Cocoa Applications & Bundles

Example project available on GitHub – CIModalSheetDialog

One thing I wanted to be able to use during the development of Slider 2 was modal dialogs that slide out from the top of Rapid Weaver. This was troublesome for me as Slider 2 is a bundle that runs within Rapid Weaver – not as a standalone application.

Googling and experimentation allowed me to find a solution for problem that I find acceptable. Below is my implementation of a modal sheet dialog, further down one may find some usage examples.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//
//  SModalSheetDialogs.h
//
//  Created by Michael Robinson on 7/08/11.
//  Copyright 2011 Code of Interest. All rights reserved.
//
#import <Foundation/Foundation.h>
 
@interface SModalSheetDialogs : NSObject
 
+ (void) showModalMessage:(NSString *)message withInformativeText:(NSString *)informativeText 
                withStyle:(NSAlertStyle)style fromSender:(id)sender;
 
+ (void) showModalRequest:(NSString *)message withInformativeText:(NSString *)informativeText 
                withStyle:(NSAlertStyle)style andButtons:(NSArray *)buttons 
           andContextInfo:(void *)contextInfo fromSender:(id)sender;
 
@end

And the implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//
//  SModalSheetDialogs.m
//{filelink=1}
//  Created by Michael Robinson on 7/08/11.
//  Copyright 2011 Code of Interest. All rights reserved.
//
 
#import "SModalSheetDialogs.h"
 
@implementation SModalSheetDialogs
 
+ (void) showModalMessage:(NSString *)message withInformativeText:(NSString *)informativeText 
                withStyle:(NSAlertStyle)style fromSender:(id)sender {
 
    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
 
    [alert addButtonWithTitle:@"OK"];
 
    [alert setInformativeText:informativeText];
    [alert setMessageText:message];
 
    [alert setAlertStyle:style];
 
    [alert beginSheetModalForWindow:[NSApp mainWindow] 
                      modalDelegate:sender 
                     didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) 
                        contextInfo:nil];
}
 
+ (void) showModalRequest:(NSString *)message withInformativeText:(NSString *)informativeText 
                withStyle:(NSAlertStyle)style andButtons:(NSArray *)buttons 
           andContextInfo:(void *)contextInfo fromSender:(id)sender {
 
    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
 
    NSEnumerator *e = [buttons objectEnumerator];
    id object;
    while (object = [e nextObject]) {
        [alert addButtonWithTitle:(NSString*)object];
    }
 
    [alert setInformativeText:informativeText];
    [alert setMessageText:message];
 
    [alert setAlertStyle:style];
 
    [alert beginSheetModalForWindow:[NSApp mainWindow] 
                      modalDelegate:sender 
                     didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) 
                        contextInfo:contextInfo];
 
}
 
@end

Showing a dialog is simple, example below.

1
2
3
4
[CIModalSheetDialog showModalMessage:@"Dialog title" 
                     withInformativeText:@"Dialog content" 
                               withStyle:NSInformationalAlertStyle 
                              fromSender:self];

This is fine for cases where we’re just informing the user that something happened – in these cases we don’t care what the user does, as the only option they have is to acknowledge the message. In more complex cases, where the user is presented with a choice, we need to have a way to catch that choice.

As the object handling the user’s choice may differ across use cases, we set the sender (as above). It is expected that this sender implements the following delegate method:

- (void) alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo

An example, first of showing a more complex dialog, with two buttons:

1
2
3
4
5
6
[CIModalSheetDialog showModalRequest:@"Dialog title" 
                     withInformativeText:@"Dialog content" 
                               withStyle:NSInformationalAlertStyle
                              andButtons:[NSArray arrayWithObjects:@"OK", @"Cancel", nil]
                          andContextInfo:@"Some identifying information"
                              fromSender:self];

And our implentation of alertDidEnd:

1
2
3
4
5
6
7
8
9
- (void) alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
    if (contextInfo == @"Some identifying information") {
        if (returnCode == NSAlertSecondButtonReturn) { // OK button was clicked
            // Handle OK
        } else {
            // Handle Cancel
        }
    }
}
No comments | Trackback

More Informative NSLog alternative!

After becoming used PHP’s very many debugging and error logging tools, I started to find NSLog somewhat lacking…

When I finally bothered to search for an alternative, and ended up with CCLog, A Variation on NSLog().

CCLog shows much more information, quoting the developer:

My preferred output would be capable of doing this:

  1. Show on which thread CCLog() was called from.
  2. Show from which sourcecode file CCLog() was called from.
  3. Show the linenumber in the sourcecode file CCLog was called from.

I’ve been using it for some time now, and recommend it without reservation!

No comments | Trackback

Detecting Presence of a Framework Within an NSBundle (Plugin)

Introduction
Jump to Tutorial

In light of Cocoa Warning: Implicitly declaring C library function ‘objc_getClass’, this tutorial has been updated.

Recently, it was brought to my attention that my method of including a particular framework (Sparkle) inside a plugin was wrong – the way I was doing it (blindly including the framework as if my plugin was the boss) was causing conflicts within the application in certain situations.

Instead, I should have been ‘sniffing’ for the presence of the framework within my plugin, and only if it wasn’t present, loading the framework dynamically. This is instead of: pretending my plugin was a real application, and including the framework in the traditional manner.

As I’m no Cocoa genius, I didn’t know what set of functions / objects one should use to implement the above process. I Googled, and luckily my searches led me on a path that ended with this: Click to Flash’s GitHub page for a specific commit intended to solve this problem: Plugin implementation.

Tutorial
In an effort to provide aid to anyone who winds up in a similar situation, the following is a short tutorial outlining the exact steps required to integrate this functionality into a plugin.

Required Steps

  • [1, 2, 3] Include an internal version of the Sparkle framework in a way that doesn’t cause it to be automatically loaded – to avoid cases where the framework has already been loaded by the application / another plugin
  • [4] Check for Sparkle at initialisation, and load an internal version if required
  • [4] (Optional) Store the Sparkle updater object internally for use in in the “checkForUpdates” IBAction

1. Download the Sparkle framework
2. Using XCode now, Drop the Sparkle.framework folder into your plugin’s Resources folder
3. Right-click on your plugin’s target & create a new “Copy Files” build phase, set it’s destination to “Resources”. Drag the Sparkle.framework from your “Resources” folder into this new build phase.
4. In the header for whatever class you want to perform the loading, in this case SomeClass.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
#import <Sparkle/Sparkle.h>
 
@interface SomeClass { 
	// For convenience, this is set to the SUUpdater object for this plugin (iff Sparkle was loaded)
	id sparkle;
}
 
@property (retain) id sparkle;
 
- (void) loadSparkle;
- (IBAction) checkForUpdates:(id)notifier;
 
@end

And the implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@implementation SomeClass
 
@synthesize sparkle;
 
- (id) init{
    if ((self = [super init]) != nil){
 
		@try {
			[self loadSparkle];	// Load the framework
		}
		@catch (NSException * e) {
			SLog(@"SomeClass init: %@", e);    // http://pagesofinterest.net/blog/2011/09/more-informative-nslog-alternative/
		}
	}
    return self;
}
 
- (void) awakeFromNib{
	@try {
		if (nil != [self sparkle]) {	// If sparkle was loaded, check for updates
			[[self sparkle] setAutomaticallyChecksForUpdates:YES];
			[[self sparkle] resetUpdateCycle];
		}
	}
	@catch (NSException * e) {
		SLog(@"SomeClass awakeFromNib: %@", e);    // http://pagesofinterest.net/blog/2011/09/more-informative-nslog-alternative/
	}
}
 
- (void) loadSparkle {
 
	@try {
		if (!NSClassFromString(@"SUUpdater")) {	// If Sparkle hasn't been loaded, attempt to load it
			NSString *frameworksPath = [[[NSBundle bundleForClass:[self class]] bundlePath] 
										stringByAppendingPathComponent:@"Contents/Resources/Sparkle.framework"];	
			NSBundle *sparkleFramework = [NSBundle bundleWithPath:frameworksPath];
			NSError *error = nil;
 
			BOOL loaded = [sparkleFramework loadAndReturnError:&error];
 
			if (loaded) {
				NSBundle *pluginBundle = [NSBundle bundleWithIdentifier:@"com.youridentifier.yourplugin"];
				NSAssert(pluginBundle, nil);
				Class updaterClass = NSClassFromString(@"SUUpdater");
				NSAssert(updaterClass, nil);
				[self setSparkle:[updaterClass updaterForBundle:pluginBundle]];
				NSAssert([self sparkle], nil);
			}
			if (error) SLog(@"error loading Your Plugin's Sparkle: %@", error);    // http://pagesofinterest.net/blog/2011/09/more-informative-nslog-alternative/
		} else {	// Sparkle has been loaded already, use the loaded version
			[self setSparkle:[SUUpdater updaterForBundle:[NSBundle bundleForClass:[self class]]]];
		}
	}
	@catch (NSException * e) {
		SLog(@"SomeClass loadSparkle: %@", e);    // http://pagesofinterest.net/blog/2011/09/more-informative-nslog-alternative/
	}
}
 
- (IBAction) checkForUpdates:(id)notifier{
	if (nil != [self sparkle]) {	// Check for updates!
		[[self sparkle] checkForUpdates:nil];
	}	
}
 
@end

Link your “Check for Updates” button to the “checkForUpdates” IBAction.

Success!

Your plugin should now be able to operate in both environments where Sparkle is missing, and where Sparkle is loaded before your plugin has been initialised.

Comments (1) | Trackback