[[JSTalk application:'Hibari'] tweet:'Hello World!'];
There are more examples in its advanced section of the user guide.
[[JSTalk application:'Hibari'] tweet:'Hello World!'];
Vsevolod Vlasov has been a major contributor to the recent improvements to the Web Inspector. Please join me in congratulating Vsevolod on his new role as a WebKit reviewer!
One of my current projects in an event management system that generates mobile apps for the attendees of the event. Can’t wait to talk more about this project but that’s it in a nutshell.
In our iOS version we have a bookmark system. When a users goes to bookmark an event we present a dialogue asking if they’d like to enable a feature that would “mirror” their bookmarks onto the system’s calendar.
Question: Is this a good practice? Its seems like too aggressive of a feature to leave on by default but considering our users won’t have a long relationship with the app it’d be rare they discover this on their own. I’ve suggested we try to track usage of this feature so we can make a more informed default later and maybe drop the dialogue entirely. Thoughts?
The alert we currently show in beta is pretty bad.
**Save to calendar?**
Would you like to automatically
save bookmarks to your Calendar
app? (You can change this
preference later in your device
settings)
[Save to Calendar] [No]
Things I don’t like about this:
A new alternative:
**Sync Bookmarks to Calendar?**
Syncing will show all the events
you bookmark in the Calendar app.
[Sync] [Cancel]
I like the word sync better than save since we do in fact keep those calendar listings up to date with changes that come down the wire. I feel like dropping the whole “this can be changed later in preferences” message helps the alert feel less intimidating. The setting panel itself is actually a high level menu in our navigation controller. It’s not too hard to find if you go looking for it and this Calendar feature is the first setting listed.
Question: What do you think of my new alert dialogue? Have any further suggestions?
While it can sometimes feel excessive to analyze single alerts like this I do feel that small improvements do add up to make the overall app experience better. Hopefully you enjoyed a little insight into my own UI refactoring workflow.
By now I am somewhat famous for my slowness in adopting the iOS platforms for my major apps: MarsEdit and Black Ink. The truth is I have been working on these releases for years, but it’s also true that work has been intermittent as I refocus on Mac versions of my apps and on other commitments in my life.
One of those other commitments has been raising a small family. My son, Henry, will become a big-brother in a matter of hours or days, and in honor of that I’ve decided to update my only shipping iOS app: Shush.
Shush 1.0 was a simple, dare I say embarrassingly simple, project that came out of our interest as new parents in Harvey Karp’s Happiest Baby on the Block techniques for soothing infants in the first few months of life. Among the bag of tricks is shushing the baby, creating white noise with your mouth: “Shhh.” While this trick worked for us, it became a little exhausting to make the noise for as long as it seemed helpful to Baby Henry.
For the new baby, I anticipate using Shush again, so I decided to give it a facelift. I had imagined over the years since I first released it that it would be fun to have it embrace some of the iPhone’s playfulness and provide a highly skeuomorphic television-set style design. This is Shush 2.0:

Don’t get me wrong: I know I won’t win any graphic-design awards for this, although it represents a peak of my skills in that area. Those of you who remember Shush 1.0 will probably consider this at least slightly more visually appealing. I tried to maintain the simplicity of Shush 1.0 while livening up the interface. I actually simplified a bit by removing the “Start/Stop” button. To turn Shush 2.0 off, you just slide the volume to its lowest position. To make the TV metaphor work, Shush is locked to landscape orientation. But I positioned the slider so it would be easily and intuitively navigated with the thumb while “holding the phone wrong” in an upright position.
Also new in this release are Shush’s ability to make static noise in the background while you continue to use your iPhone or iPad. I imagine this will be handy especially for parents who want to produce that sweet, soothing static, but would also like to so catch up on Instapaper, Twitter, or whatever while they’re cradling the baby.
Finally, that skeuomorphic television static actually provides something of a hypnotic animated effect. Some users may find the visual display useful either for lulling themselves or for distracting and amusing a baby. From a technical standpoint I’m particularly proud of the effect. Inspired by a suggestion from Mike Ash, I implemented the static animation as an OpenGL Shader, so it runs almost entirely on the iPhone’s GPU. This means it is extremely efficient and not liable to slow down your phone or gobble up your battery. If you don’t like the TV static or just want to save even more power while Shushing, you can put the display to sleep and Shush keeps on Shushing.
I hope you enjoy Shush 2.0. Let me know if you give it a try or have feedback about my decisions in redesigning this simple application. To answer the inevitable question: yes, MarsEdit and Black Ink are still under development for iOS!
A few months ago I wrote here about a generic approach to safely take incoming JSON and save values to Core Data object. The goals of that code were twofold:
-setValuesForKeysWithDictionary: for use with NSManagedObject and its subclassesThe first item was the most important. It’s tempting to use -setValuesForKeysWithDictionary: to transfer JSON to a model object in one step. The method runs through the dictionary and calls -setValue:forKey: on the target object for every entry. It has a fatal flaw though, in that it doesn’t check to see if the target object actually has a key before trying to set it. Using this method when you don’t have absolute control over the dictionary contents is an invitation to unknown key exceptions and nasty app crashes.
Fixing this for managed objects was relatively easy because Core Data provides convenient Objective-C introspection methods. The general approach was:
-setValue:forKey: with that key and its value.And then just last week I had the thought, wouldn’t it be nice if this worked for any object, not just for managed objects?
Since Objective-C is dynamic, pretty much everything you’d want to know about a class is available at run time. I’m not just talking about methods like -respondsToSelector: and -isKindOfClass:, though those are extremely useful. You can go much, much deeper than that, inspecting (and even changing) every aspect of a class’s implementation. Much of this happens via C function calls rather than Objective-C method calls. The Objective-C runtime is not actually written in Objective-C, and it’s the runtime that has the information.
To update the code for use with objects that don’t inherit from NSManagedObject the new code looks through the properties declared on a class and uses those to run through the incoming JSON. The general approach is the same but the implementation uses NSObject properties instead of NSEntityDescription attributes.
It’s also possible to look through the instance variables instead of the properties. Often this would amount to the same thing. Where they differ is when the backing ivar has a different name, i.e. when you’re using something like:
@synthesize foo = __myReallyBizarrePrivateName____;
In that case iterating over properties would find foo while iterating over instance variables would find __myReallyBizarrePrivateName____. Either is valid but I’m going with the properties because (at least for me) they’re more likely to match up with the JSON keys.
A simple version that meets requirement #1 looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | - (void)setValuesForKeysWithJSONDictionary:(NSDictionary *)keyedValues dateFormatter:(NSDateFormatter *)dateFormatter { unsigned int propertyCount; objc_property_t *properties = class_copyPropertyList([self class], &propertyCount); for (int i=0; i<propertyCount; i++) { objc_property_t property = properties[i]; const char *propertyName = property_getName(property); NSString *keyName = [NSString stringWithUTF8String:propertyName]; id value = [keyedValues objectForKey:keyName]; if (value != nil) { [self setValue:value forKey:keyName]; } } free(properties); } |
This starts with a call to class_copyPropertyList(), which gets a C-style array of property declarations for the requested class. The propertyCount argument indicates how many properties are in the array. The array contains zero or more objc_property_t entries, which is an opaque structure.
The code iterates through this array. For each one it uses property_getName to get the property name as a C-style string. Then it converts this to an NSString and uses that to look up entries in the incoming dictionary. And, voila, we’re using the class’s own properties to look up values in the dictionary instead of the other way around.
A final detail– unusual in Objective-C code– is the call to free(). Since class_copyPropertyList() has copy in its name, the calling code is responsible for disposing of the returned data. And since it’s a C call, this needs to be done C style. This call would need to be there even if the project were using ARC.
The great thing about this solution is that it’s not the non-managed-object alternative to the previous version, it’s a direct replacement. This approach works just as well on managed objects as on other objects– provided, that is, that you create custom subclasses of NSManagedObject for your entities that declare properties for managed object attributes. So long as the properties exist, the code works. If you’re using Xcode or mogenerator to generate your managed object subclasses, you’re covered. If you aren’t creating custom subclasses, first of all, why not? But in that case this approach won’t work since NSManagedObject doesn’t have the necessary property declarations.
Some of you may have noticed that it’s possible to do the same thing without any mucking about with the runtime by doing something like this:
1 2 3 4 5 6 7 8 | for (NSString *key in keyedValues) { @try { [self setValue:[keyedValues objectForKey:key] forKey:key]; } @catch (NSException *exception) { // Do nothing } } |
In this case the dictionary keys still drive the action, but exception handling means the code doesn’t crash on unknown keys. So why bother then? Because of requirement #2 above. Coercing JSON into appropriate data types is going to require introspection. With this simplified approach you don’t crash, but you also don’t get type conversions. If not crashing is all you’re interested in, this works just as well and is probably faster. It’s certainly simpler anyway. It’s not going to do what I need though.
To meet requirement #2 the code needs to go deeper. As with the Core Data implementation, it needs to look up the expected value for the property and compare that to the type of the incoming data. To do this I’ll expand the if block beginning on line 12 above to look like this:
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 | if (value != nil) { char *typeEncoding = NULL; typeEncoding = property_copyAttributeValue(property, "T"); if (typeEncoding == NULL) { continue; } switch (typeEncoding[0]) { case '@': { // Object Class class = nil; if (strlen(typeEncoding) >= 3) { char *className = strndup(typeEncoding+2, strlen(typeEncoding)-3); class = NSClassFromString([NSString stringWithUTF8String:className]); } // Check for type mismatch, attempt to compensate if ([class isSubclassOfClass:[NSString class]] && [value isKindOfClass:[NSNumber class]]) { value = [value stringValue]; } else if ([class isSubclassOfClass:[NSNumber class]] && [value isKindOfClass:[NSString class]]) { // If the ivar is an NSNumber we really can't tell if it's intended as an integer, float, etc. NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; value = [numberFormatter numberFromString:value]; [numberFormatter release]; } else if ([class isSubclassOfClass:[NSDate class]] && [value isKindOfClass:[NSString class]] && (dateFormatter != nil)) { value = [dateFormatter dateFromString:value]; } break; } default: { break; } } [self setValue:value forKey:keyName]; free(typeEncoding); } |
The runtime provides information about properties via the call to property_copyAttributeValue() on line 3. The first argument is the property of interest. The second one can have a bunch of different values depending on what you want to look up. A “T” requests a C-style string that describes the property type. For full details on what you can do with the second argument, see the (somewhat out of date as of this writing) “Declared Properties” section of Apple’s Objective-C Runtime Programming Guide.
If the property type is a pointer to a class, the return value will be something like T@"NSNumber", T@"NSString", T@"MyClass", etc. The next thing the code does then is to check for a leading @ and, if found, set about getting the Class that the type string names. This happens in lines 12-16. The code strips off the @ and the quotes, converts to NSString, and uses NSClassFromString to get the Class.
The rest of this code is remarkably similar to the Core Data version, looking for type mismatches and converting the incoming value where needed. The chief difference is that it uses -isSubclassOfClass: to check on the expected type of the property instead of looking at the Core Data-specific NSAttributeType.
Except for one important difference. For numeric properties, the Core Data attribute type would indicate whether a floating point or integer value was expected. With NSNumber we have no way of knowing. So the code uses NSNumberFormatter to parse incoming strings and leaves it at that. If this kind of mismatch occurs then there are bigger problems anyway. You could change the code to round a float to an int, but is that actually going to be a valid result? Maybe, maybe not.
The type comparisons in this code could go on forever but in this case the code is specifically looking for problems that sometimes crop up with JSON.
Since property_copyAttributeValue() has copy in its name, this block adds another call to free() to clean up after itself.
But what if the expected value is not an object at all? What if it’s a primitive int? Fortunately property_copyAttributeValue() covers that case as well. In this case the type encoding string is shorter, with values like Ti for int, Tf for float, TQ for unsigned long long, etc (again, see Apple’s docs for a full list).
With this information, the switch statement above can be expanded with a few more cases.
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 | case 'i': // int case 's': // short case 'l': // long case 'q': // long long case 'I': // unsigned int case 'S': // unsigned short case 'L': // unsigned long case 'Q': // unsigned long long case 'f': // float case 'd': // double case 'B': // BOOL { if ([value isKindOfClass:[NSString class]]) { NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; value = [numberFormatter numberFromString:value]; [numberFormatter release]; } break; } case 'c': // char case 'C': // unsigned char { if ([value isKindOfClass:[NSString class]]) { char firstCharacter = [value characterAtIndex:0]; value = [NSNumber numberWithChar:firstCharacter]; } break; } |
The first half of this, up to line 20, handles all the numeric types. As with the previous code block there’s no attempt to work out floating point/integer conflicts since it’s impossible to know how to handle this in the general case. It would be possible to break this up into more specific checks, say by using -[NSString intValue] when an integer result is expected and hope for valid data. But the code above handles the case where you actually have an integer value, and if you don’t have one then again, you have bigger problems.
The rest of this block handles a primitive char property by taking the first character in the incoming string. Longer strings can’t be stored in a char anyway, so this is the best approach.
In both cases the conversion results in an NSNumber instead of a primitive type. That’s OK though– -setValue:forKey: has our back here and will unbox the object for us.
Using this approach makes it a lot easier to deal with web services. You can’t always rely on the results matching what you expect (or what’s documented). Inspecting classes at run time takes a more defensive approach to dealing with data you can’t control.
A category on NSObject that implements this code can be found at github.
I’ve been hard at work on Fortunate Bear’s next product, but I wanted to lift my head long enough to give a quick preview of what I’m working on. Keep in mind, it’s very early in development and maybe 20% (if that) of the features are done. But without further ado:
(Video is HTML 5, so you’ll need a recent browser.)
As a bonus you get to see my fantastical art skills. The codename for the project is Vectorspring, and I hope to be showing off more as it progresses.
"VoodooPad is a powerful note-collecting, wikifying, productivity machine. With the right scripts, you can make just about anything happen, at least within the realm of text, images and web pages. VoodooPad has a built in web server, and can export your notebook as a full website, with links intact. I like the web export the best, because it gives me a crazy amount of control over the output. To that end, I built a website using nothing but VoodooPad, hopped up on some custom scripts."
If learning to program is even a minor goal for you, Code Year (via Brent Simmons) might be just the encouragement you need. They promise to email you on a weekly basis with coding lessons to help you achieve your goal.
I’m one of those computer programmers who downplays the difficulty of the profession, because “if I can do it, anybody can do it!” On the other hand, I have faced challenges that made me question whether I’m vaguely qualified for the job. What it boils down to is that programming is both incredibly simple and impossibly hard, like so many important things in life.
There was a time when nobody knew how to write literary prose. The geniuses who invented it shared their special tool with a few friends, and they relished in their private, elite communications. Eventually monks, politicians, and academics joined the club. Now, we judge a society’s overall level of intellectual advancement by the literacy rate: the percentage of people who have learned to read and write.
Literacy isn’t about becoming a Hemingway or a Chabon. It’s about learning the basic tools to get a job done. I think programming — coding — is much the same. You don’t have to be the world’s best programmer to develop a means of expressing yourself, of solving a problem, of making something happen. If you’re lucky, you’ll be a genius, but you start out with the basics.
Long ago, it would have been ridiculous to assume a whole society could be judged by its ability to read and write prose. It feels ridiculous now, to assume that we might use computer programming as a similar benchmark. Yet it may happen.
Did you always mean to learn another language, but never did? By all means, learn Spanish, French, or Chinese. But learn to code, too.
In this post, I'll look at drawing a detailed image in code by combining multiple visual elements. Unlike previous posts I've done on drawing in Cocoa, this will focus on the AppKit classes. The code will use NSGraphicsContext, NSBezierPath, NSAffineTransform, NSGradient, NSGlyph and show you some simple ways to export the contents of an NSView to a file.
I was reading an article on the Mac App Store and instead of paying attention to the article or working (which is what I was supposed to be doing at the time) I found myself staring at the accompanying image (a large version of the Mac App Store icon). Being the Cocoa programmer that I am, I found myself mentally deconstructing the icon and wondering how you'd draw something similar in Cocoa.
Only after I'd written the code and decided to write a post on it did I pay attention to the fact that I've already written a series of posts on creating icons. I'm not trying to be repetitive (this post is about Cocoa drawing techniques; it is not a suggested way to create application icons as the previous post was) but now that it's too late to change, I wish I had chosen a more novel visual subject for this post.
Update 2011-05-28: this post is a Mac application. If you'd like to see the same design drawn using iOS CoreGraphics code, check out this blog post by Marcus Crafter.
You can download the complete sample project used in this post here IconApp.zip (96kB)
The application has a single window into which it draws an icon. You can resize the window and the icon always scales to fit. You can export the icon to a PDF or PNG file.
Obviously, while inspired by the Mac App Store icon, this design is not trying to accurately replicate it. The real purpose is to show how to achieve the multiple, layered gradients, curve drawing, path-based clipping, shadowing and scaling you're likely to need if you want to create a non-trivial design in code.
Even before we start drawing, we need to ensure that the image will always be scaled to fit the window. The first step is to find a scale that will fit the square icon into the view regardless of view size or aspect ratio:
NSSize nativeSize = [self nativeRect].size;
NSSize boundsSize = self.bounds.size;
CGFloat nativeAspect = nativeSize.width / nativeSize.height;
CGFloat boundsAspect = boundsSize.width / boundsSize.height;
CGFloat scale = nativeAspect > boundsAspect ?
boundsSize.width / nativeSize.width :
boundsSize.height / nativeSize.height;In this case, the nativeRect of the view is defined as NSMakeRect(0, 0, 512, 512);
Once we have the desired scale factor, we resize the current drawing context and center it:
NSAffineTransform *transform = [[NSAffineTransform alloc] init];
[transform
translateXBy:0.5 * (boundsSize.width - scale * nativeSize.width)
yBy:0.5 * (boundsSize.height - scale * nativeSize.height)];
[transform scaleBy:scale];
[transform set];Once we've applied this affine transform, we can draw as though our canvas is the nativeRect size but it will perform an aspect fit for any view size.
I haven't shown it here but if you change the current transform (or the current clipping path as I also do later) you should remember to place a call to [[NSGraphicsContext currentContext] saveGraphicsState] before your changes and follow your drawing by a call to [[NSGraphicsContext currentContext] restoreGraphicsState] to put everything back again.
We draw the background circle initially using a flat color, with an NSShadow enabled.
We use a flat color while drawing the shadow (instead of the gradient that we want in the final output) because the gradient drawing method we're going to use actually clips to the gradient's boundary, so the shadow wouldn't be drawn (because the shadow would be outside the clipping boundary).
Also, gradients don't anti-alias their edges. Drawing the shape with a flat color that is approximately the average color of the gradient will give us a nice, anti-aliased edge.
[NSShadow setShadowWithOffset:NSMakeSize(0, -8 * scale) blurRadius:12 * scale
color:[NSColor colorWithCalibratedWhite:0 alpha:0.75]];
[[NSColor colorWithCalibratedWhite:0.9 alpha:1.0] set];
[[NSBezierPath bezierPathWithOvalInRect:ellipseRect] fill];
[NSShadow clearShadow];Coding practice aside: If you're a good coder, you should avoid "magic numbers". Magic numbers are unnamed numbers used without explanation in the code. They are considered bad practice (instead, you should assign values to a constant that names the value and explains any derivation, then you may use it).
However, I normally make an exception for drawing code (as I have in this case). If a number is chosen purely for aesthetic purposes and has no real relationship to any other value, I leave it magic on the assumption that its aesthetic purpose (with derivation or geometric relationship) is obvious.
You do need to keep a tight reign on permissive number usage though. You'll notice lower down in this post (in the "Gloss Gradient" code), where there's a blend of calculated, derived and relationship-related values as well as purely aesthetic values, I've bothered to use named constants instead, to clarify what is derived, what is proportionate and which arc is which.
The shadow methods in the previous code block are convenience methods from a category implemented as follows:
@implementation NSShadow (SingleLineShadows)
+ (void)setShadowWithOffset:(NSSize)offset blurRadius:(CGFloat)radius
color:(NSColor *)shadowColor
{
NSShadow *aShadow = [[[self alloc] init] autorelease];
[aShadow setShadowOffset:offset];
[aShadow setShadowBlurRadius:radius];
[aShadow setShadowColor:shadowColor];
[aShadow set];
}
+ (void)clearShadow
{
NSShadow *aShadow = [[[self alloc] init] autorelease];
[aShadow set];
}
@endWe then draw the gradient over the top:
NSBezierPath *ellipse = [NSBezierPath bezierPathWithOvalInRect:ellipseRect];
NSGradient *borderGradient =
[[[NSGradient alloc]
initWithStartingColor:[NSColor colorWithCalibratedWhite:1.0 alpha:1.0]
endingColor:[NSColor colorWithCalibratedWhite:0.82 alpha:1.0]]
autorelease];
[borderGradient drawInBezierPath:ellipse angle:-90];Single, simple gradients tend to look bland and artificial. Our eyes expect much more complex lighting than a single gradient normally provides.
For this gradient, I wanted a pear shape as though a circular gradient had been smudged upwards. NSGradient does have methods that allow the gradient to be drawn as a circle along a path but the aesthetic is harsher than the soft, blurry look I wanted.
The easiest way to get a natural, soft-lighting style gradient effect is simply to lay a few gradients on top of each other. By getting the alpha transparency of each gradient right, the gradients will all appear to be part of the same effect. Of course, this is also slow, particularly at high resolutions, so it is an approach to use sparingly.
The final result in the bottom right corner of this image is achieved by drawing a black background, then drawing the top-left, top-right and bottom-left gradients over the top of the black background.
Here's the code to paint the center of the circle black. We then set the clipping path of the context so that all further drawing will be clipped to inside the border of the circle.
NSRect ellipseCenterRect = NSInsetRect(ellipseRect, 16, 16); [[NSColor blackColor] set]; NSBezierPath *ellipseCenter = [NSBezierPath bezierPathWithOvalInRect:ellipseCenterRect]; [ellipseCenter fill]; [ellipseCenter setClip];
Now that the code is complete, I realize that I didn't need to clip here. I could have used the -[NSBezierPath drawInBezierPath:relativeCenterPosition:] method for the gradients to automatically draw them clipped to the .ellipseCenter path and they would have clipped themselves (nothing else would have needed clipping). Performance-wise, it doesn't really matter though (either I apply the clip or the method does).
Once the background is drawn, we begin layering the gradients. This is the code that draws the top-left of the gradients shown above:
NSGradient *bottomGlowGradient =
[[[NSGradient alloc]
initWithColorsAndLocations:
[NSColor colorWithCalibratedRed:0 green:0.94 blue:0.82 alpha:1.0], 0.0,
[NSColor colorWithCalibratedRed:0 green:0.62 blue:0.56 alpha:1.0], 0.35,
[NSColor colorWithCalibratedRed:0 green:0.05 blue:0.35 alpha:1.0], 0.6,
[NSColor colorWithCalibratedRed:0 green:0.0 blue:0.0 alpha:1.0], 0.7,
nil]
autorelease];
[bottomGlowGradient
drawInRect:ellipseCenterRect relativeCenterPosition:NSMakePoint(0, -0.2)];The floral heart is a character from the Arial Unicode MS font. Getting the bezier path of a font character is a little cumbersome, since you need to use NSLayoutManager to get the glyph for you before you can ask NSBezierPath to create a path from the glyph.
NSString *floralHeart = @"\u2766";
NSRange stringRange = NSMakeRange(0, [floralHeart length]);
NSFont *arialUnicode =
[[NSFontManager sharedFontManager]
fontWithFamily:@"Arial Unicode MS"
traits:0
weight:5
size:345];
NSLayoutManager *layoutManager = [[[NSLayoutManager alloc] init] autorelease];
NSTextStorage *textStorage =
[[[NSTextStorage alloc] initWithString:floralHeart] autorelease];
[textStorage addAttribute:NSFontAttributeName value:arialUnicode range:stringRange];
[textStorage fixAttributesInRange:stringRange];
[textStorage addLayoutManager:layoutManager];
NSInteger numGlyphs = [layoutManager numberOfGlyphs];
NSGlyph *glyphs = (NSGlyph *)malloc(sizeof(NSGlyph) * (numGlyphs + 1)); // includes space for NULL terminator
[layoutManager getGlyphs:glyphs range:NSMakeRange(0, numGlyphs)];
[textStorage removeLayoutManager:layoutManager];
NSBezierPath *floralHeartPath = [[[NSBezierPath alloc] init] autorelease];
[floralHeartPath moveToPoint:NSMakePoint(130, 140)];
[floralHeartPath appendBezierPathWithGlyphs:glyphs count:numGlyphs inFont:arialUnicode];
free(glyphs);If there's an easier way to do this, I'd love to know. It certainly seems like you should just be able to ask for the path for a Unicode character directly.
The floral heart is then drawn using a gradient and shadow in almost the same way as the frame (we even use the same gradient).
Update: As Christopher Lloyd points out in the comments, you can get the NSGlyph more easily using the CTFont function CTFontGetGlyphsForCharacters. It saves 9 lines of code but it breaks my effort to do everything in this post using AppKit instead of the Core APIs:
// Replaces lines 1, 2, 9-18 of previous code block
NSInteger numGlyphs = 1; // hard-coded glyph count for floral heart character
NSGlyph *glyphs = (NSGlyph *)malloc(sizeof(NSGlyph) * (numGlyphs + 1)); // includes space for NULL terminator
CTFontGetGlyphsForCharacters(
(CTFontRef)arialUnicode, (const UniChar *)L"\u2766", (CGGlyph *)glyphs, numGlyphs);The icon is finished off with a gloss gradient. The gloss gradient is drawn between two arcs and it is these arcs that are the tricky part.
The gloss itself is inset from the frame to ensure that between the nearly white color of the frame and the gloss gradient, we still get a dark rim for contrast.
The left and right edges of the gloss start just above the middle of the circle (I chose 0.02π radians), following the arc of the circle at the top and through the middle of the image it bulges down to approximately the middle of the circle.
I got the trignometry for the top arc correct but I didn't really bother with the bottom bulging arc (since its exact placement doesn't really matter. Due to the nature of the 3 point arc drawing method I used for this bottom arc, if you get the radius wrong for the three point locations, the arc either straightens out near the left and right endpoints or the arc overshoots these points. I'm sure I could have done this better (a bézier curve might have been a smarter choice since a proper circle was not required for this curve).
const CGFloat glossInset = 8;
CGFloat glossRadius = (ellipseCenterRect.size.width * 0.5) - glossInset;
NSPoint center = NSMakePoint(NSMidX(ellipseRect), NSMidY(ellipseRect));
double arcFraction = 0.02;
NSPoint arcStartPoint = NSMakePoint(
center.x - glossRadius * cos(arcFraction * M_PI),
center.y + glossRadius * sin(arcFraction * M_PI));
NSPoint arcEndPoint = NSMakePoint(
center.x + glossRadius * cos(arcFraction * M_PI),
center.y + glossRadius * sin(arcFraction * M_PI));
NSBezierPath *glossPath = [[[NSBezierPath alloc] init] autorelease];
[glossPath moveToPoint:arcStartPoint];
[glossPath
appendBezierPathWithArcWithCenter:center
radius:glossRadius
startAngle:arcFraction * 180
endAngle:(1.0 - arcFraction) * 180];
const CGFloat bottomArcBulgeDistance = 70;
const CGFloat bottomArcRadius = 2.6;
[glossPath moveToPoint:arcEndPoint];
[glossPath
appendBezierPathWithArcFromPoint:
NSMakePoint(center.x, center.y - bottomArcBulgeDistance)
toPoint:arcStartPoint
radius:glossRadius * bottomArcRadius];
[glossPath lineToPoint:arcStartPoint];Exporting a view to a PDF file is extremely simple in Cocoa:
[[iconView dataWithPDFInsideRect:[iconView nativeRect]]
writeToURL:[savePanel URL]
atomically:YES];However, you'll quickly see that gradients with transparency don't output correctly in Mac OS X (everything else, including the shadows, work fine). The internets claim it's a limitation of PDF 1.4 (the PDF standard used in Mac OS X).
Instead we need to export to PNG if we want to keep the transparency. There's a few different ways to do this but the quickest and easiest is:
NSRect iconViewFrame = iconView.frame;
[iconView setFrame:[iconView nativeRect]];
NSBitmapImageRep *bitmapImageRep =
[iconView bitmapImageRepForCachingDisplayInRect:[iconView frame]];
[iconView
cacheDisplayInRect:[iconView bounds]
toBitmapImageRep:bitmapImageRep];
[[bitmapImageRep representationUsingType:NSPNGFileType properties:nil]
writeToURL:[savePanel URL]
atomically:YES];
[iconView setFrame:iconViewFrame];This approach contains a few potential problems:
Ultimately, creating your own NSBitmapImageRep, setting the NSGraphicsContext using graphicsContextWithBitmapImageRep:, locking focus yourself and invoking drawRect: directly will avoid these issues and is more flexible. But it would have been more work so I didn't bother for this sample project (I mostly put this export code into the app so I could create the app's icon).
You can download the complete sample project used in this post here IconApp.zip (96kB)
Ultimately, it's uncommon to construct this many visual elements in code; these things are invariably easier in a drawing program and a prerendered bitmap would be faster (this is quite slow at large sizes).
However there are many situations where drawing in code can offer advantages — particularly if your designs need to reshape or adapt to fit content. Buttons and other controls containing text are a an example, particularly if your design cannot simply be stretched.
Even if the most complicated thing you need to do is anti-alias a path filled by a gradient, there are a few tricks contained in this post that I hope will help.
by Matt Gallagher (noreply@blogger.com) at January 01, 2012 03:58 PM
Last fall I got a new iMac that I only use during the Weekends. I’ve just recently noticed that I still didn’t have Code Signing running. I’ve done quite some searching and even tried to debug what codesign does when it tries to match certificates. Only then I noticed that even though I had all certificates in place, the problem was that these Certificates needed the private key as well. The fix was as simple as exporting and importing the private key from one mac to the other.
As I’ve had problems with Code Signing before, I decided that it’d be a lot more helpful to not have to google around the webs like crazy over and over again but instead have some app that contains the knowledge and gives proper tips.
The result is CertificateHelper, which you can find over at GitHub. Currently it only supports those problems that I was having (no certificates/no private key).
To add new Problems to the app, simply subclass CertificateProblem and implement the following methods:
I think the most problematic part of the implementation is the work with the keychain. There’s the CertificateAccess class right now which tries to simplifies that.
Karsten