Using Frameworks Within NSBundles

I wanted to use Connection Kit for my current project, but ran into issues as it requires a few other frameworks to function. Many months ago I worked around this by copying the internal framework’s source files directly into Connection Kit.

Everything went well until I updated Connection Kit. Suddenly it required more internal frameworks, and copying their source became impractical.

Comments (5) | Trackback

Resizing NSTextField to Fit Content

The NSPopOver, a new addition to the Cocoa toolkit since OS X 10.7, is an excellent way to give the user feedback on actions requiring their input. In my use case, I needed to be able to show an NSPopOver containing a multiline label populated with a string of varying length.


Getting the NSPopOver, NSView & NTextField all working perfectly was very basic.


Ensuring that the NSPopOver’s NSView & NSTextField resized appropriately was more challenging.


I needed to get the rectangle that my string would fill when drawn. A short Google search led me here: SO: NSString sizeWithAttributes: Content Rect. As I know I’ll be doing this again, I made a category of NSString that provides this.


The rest was simple:

1
2
3
4
5
6
7
8
9
10
11
NSSize size = [string sizeWithWidth:200.0 andFont:[textLabel font]];
 
[view setFrameSize:NSMakeSize(size.width + 30, size.height + 30)];
 
[textLabel setFrame:NSMakeRect(15, 15, size.width, size.height)];
[textLabel setStringValue:string];
 
popover.contentSize = view.frame.size;
[popover showRelativeToRect:[textField bounds] 
                     ofView:textField
              preferredEdge:NSMaxXEdge];

The NSString+Size category is available here: Cocoa Categories.

After doing this, I rethought my approach and streamlined it by creating a category on NSPopover, which is available in the above repository. I wrote about the process here: User Feedback & the NSPopover.

Comments (2) | Trackback

Limit NSNotifications to Notify Within NSBundle Instances Only

NSNotifications are wonderful. So long as when using them within an NSBundle, one keeps the fact that notifications are broadcast to all instances of the bundle in mind.

To limit certain notifications to be bundle instance specific, one could append a unique id to each notification name.

For notifications sent and received by one class, this is trivial:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (id) initWithRepresentedObject:(id)inObject {
	self = [super initWithNibName:@"COIMMContentView" bundle:[COIMMPlugin bundle]];
 
	if (self) {
		[self setRepresentedObject:inObject];
                optionsController = [[COIMMOptionsController alloc] init];
                uniqueNotificationId = [[NSProcessInfo processInfo] globallyUniqueString];
 
                NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
 
                [notificationCenter addObserver:self
                           selector:@selector(rwDocumentDidAddPages:)
                               name:[kCOIMMRWDocumentDidAddPagesNotification stringByAppendingString:uniqueNotificationId]
                             object:nil];
 
                [notificationCenter addObserver:self
                           selector:@selector(rwDocumentDidRemovePages:) 
                               name:[kCOIMMRWDocumentDidRemovePagesNotification stringByAppendingString:uniqueNotificationId]
                             object:nil];
	}
 
	return self;
}

This implementation would not be ideal for cases where notifications are intended to be sent and received by different classes, but for my immediate use case, it works.

Comments (2) | Trackback

Listing Methods at Runtime in Objective C

Before getting into creating my next Rapid Weaver plugin, I needed to do some experimentation to make sure what I wanted to achieve was possible. Wanting to manipulate some aspects of RWPage objects, I first required a list of methods for the RWPage class.

Thanks to Matt Gallagher’s IMP of the current method entry, I was able to generate my method list with the following code:

#import "objc/objc-class.h"
 
// Iterate over the class and all superclasses
Class currentClass = [RWPage class];
while (currentClass) {
    // Iterate over all instance methods for this class
    unsigned int methodCount;
    Method *methodList = class_copyMethodList(currentClass, &methodCount);
    unsigned int i = 0;
    for (; i < methodCount; i++) {
        NSLog(@"%@ - %@", [NSString stringWithCString:class_getName(currentClass) encoding:NSUTF8StringEncoding], [NSString stringWithCString:sel_getName(method_getName(methodList[i])) encoding:NSUTF8StringEncoding]);
    }
 
    free(methodList);
    currentClass = class_getSuperclass(currentClass);
}

Method list acquired, the process of whittling it down until I found the one I wanted began.

Comments (5) | Trackback

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