Making NSImages Resizable

AppKit has no built in support for resizable (or stretchable) images which has been bothering me for a while, so today lets look at adding it!

The interface and behaviour below is heavily modelled on that in iOS 6.

How Resizing Works

Given a single image, we chop it up into multiple parts.

image

The corners always stay exactly the same size, with only their position moving. Next the edge pieces are tiled in a single direction. Finally the center (orange) is scaled or tiled resulting in the below.

image

Adding to NSImage

While we could have likely done all this work directly in a category on NSImage. I like ivars and I don’t like adding logic directly to categories, so lets implement it in a subclass of NSImage, RHResizableImage.

That doesn’t mean we can’t provide a nice API directly on NSImage, which is what we do below, via way of a pass-through category.

Basic Process

  • Provided with an image and a set of edge or cap insets.
  • We use these to determine which parts to tile (or stretch) and which parts to lock in place.
  • Split these parts into 9 separate image pieces.
  • Override the NSImage drawInRect:fromRect:operation:fraction:respectFlipped:hints: method and draw our representation when asked, using NSDrawNinePartImage();.
  • Optionally cache our drawing.
  • Discard the cache if the size or scale we are next asked to draw is different from our cached size and scale.

The Interface

In order to model the interface on iOS as closely as possible, we had to create a few new structures.

RHEdgeInsets

typedef struct _RHEdgeInsets {
    CGFloat top, left, bottom, right;  // specify amount to inset (positive) for each of the edges. values can be negative to 'outset'
} RHEdgeInsets;

extern RHEdgeInsets RHEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right);
extern CGRect RHEdgeInsetsInsetRect(CGRect rect, RHEdgeInsets insets, BOOL flipped); //if flipped origin is top-left otherwise origin is bottom-left (OSX Default is NO)
extern BOOL RHEdgeInsetsEqualToEdgeInsets(RHEdgeInsets insets1, RHEdgeInsets insets2);
extern const RHEdgeInsets RHEdgeInsetsZero;

RHResizableImageResizingMode

typedef enum NSInteger {
    RHResizableImageResizingModeTile,
    RHResizableImageResizingModeStretch,
} RHResizableImageResizingMode;

We then proceed with the class interface as follows.

RHResizableImage Interface

@interface RHResizableImage : NSImage
...
-(id)initWithImage:(NSImage*)image leftCapWidth:(CGFloat)leftCapWidth topCapHeight:(CGFloat)topCapHeight; 

-(id)initWithImage:(NSImage*)image capInsets:(RHEdgeInsets)capInsets;
-(id)initWithImage:(NSImage*)image capInsets:(RHEdgeInsets)capInsets resizingMode:(RHResizableImageResizingMode)resizingMode; //designated initializer

@end

Implementation

So we need to do a few things. First lets implement a little utility method to create an image from a subsection of another image.

As an aside, the arc_autorelease() that you see below comes from RHARCSupport.h

RHCapturePieceOfImageFromRect

NSImage* RHCapturePieceOfImageFromRect(NSImage *image, CGRect rect){
    NSRect fromRect = NSRectFromCGRect(rect);
    NSImage *newImage = [[NSImage alloc] initWithSize:fromRect.size];
    if (newImage.isValid && fromRect.size.width > 0.0f && fromRect.size.height > 0.0f) {
        NSRect toRect = fromRect;
        toRect.origin = NSZeroPoint;
        [newImage lockFocus];
        //because we override drawInRect method in RHResizableImage, we need to call the super; non stretch implementation
        if ([image isKindOfClass:[RHResizableImage class]]){
            [(RHResizableImage*)image nonStretchedDrawInRect:toRect fromRect:fromRect operation:NSCompositeCopy fraction:1.0f respectFlipped:YES hints:nil];
        } else {
            [image drawInRect:toRect fromRect:fromRect operation:NSCompositeCopy fraction:1.0f respectFlipped:YES hints:nil];
        }
        [newImage unlockFocus];
    }

    return arc_autorelease(newImage);
}

Next up, we need a method to create all the various pieces to pass to NSDrawNinePartImage(). We can keep them around in an array.

RHNinePartPiecesFromImageWithInsets

NSArray* RHNinePartPiecesFromImageWithInsets(NSImage *image, RHEdgeInsets capInsets){

    CGFloat imageWidth = image.size.width;
    CGFloat imageHeight = image.size.height;

    CGFloat leftCapWidth = capInsets.left;
    CGFloat topCapHeight = capInsets.top;
    CGFloat rightCapWidth = capInsets.right;
    CGFloat bottomCapHeight = capInsets.bottom;

    NSSize centerSize = NSMakeSize(imageWidth - leftCapWidth - rightCapWidth, imageHeight - topCapHeight - bottomCapHeight);


    NSImage *topLeftCorner = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(0.0f, imageHeight - topCapHeight, leftCapWidth, topCapHeight));
    NSImage *topEdgeFill = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(leftCapWidth, imageHeight - topCapHeight, centerSize.width, topCapHeight));
    NSImage *topRightCorner = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(imageWidth - rightCapWidth, imageHeight - topCapHeight, rightCapWidth, topCapHeight));

    NSImage *leftEdgeFill = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(0.0f, bottomCapHeight, leftCapWidth, centerSize.height));
    NSImage *centerFill = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(leftCapWidth, bottomCapHeight, centerSize.width, centerSize.height));
    NSImage *rightEdgeFill = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(imageWidth - rightCapWidth, bottomCapHeight, rightCapWidth, centerSize.height));

    NSImage *bottomLeftCorner = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(0.0f, 0.0f, leftCapWidth, bottomCapHeight));
    NSImage *bottomEdgeFill = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(leftCapWidth, 0.0f, centerSize.width, bottomCapHeight));
    NSImage *bottomRightCorner = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(imageWidth - rightCapWidth, 0.0f, rightCapWidth, bottomCapHeight));

    return [NSArray arrayWithObjects:topLeftCorner, topEdgeFill, topRightCorner, leftEdgeFill, centerFill, rightEdgeFill, bottomLeftCorner, bottomEdgeFill, bottomRightCorner, nil];
}

Now lets piece all this together.

First in our init method, we call out and create our image pieces.

initWithImage:capInsets:

-(id)initWithImage:(NSImage*)image capInsets:(RHEdgeInsets)capInsets resizingMode:(RHResizableImageResizingMode)resizingMode{
    self = [super initWithData:[image TIFFRepresentation]];

    if (self){
        _capInsets = capInsets;
        _resizingMode = resizingMode;

        _imagePieces = arc_retain(RHNinePartPiecesFromImageWithInsets(self, _capInsets));
    }
    return self;
}

Then, each time we are asked to draw we perform the below drawing and caching logic.

drawInRect:fromRect:operation:fraction:respectFlipped:hints:

-(void)drawInRect:(NSRect)rect fromRect:(NSRect)fromRect operation:(NSCompositingOperation)op fraction:(CGFloat)requestedAlpha respectFlipped:(BOOL)respectContextIsFlipped hints:(NSDictionary *)hints{
    CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];

    //if our current cached image ref size does not match, throw away the cached image
    //we also treat the current contexts scale as an invalidator so we don't draw the old, cached result.
    if (!NSEqualSizes(rect.size, _cachedImageSize) || _cachedImageDeviceScale != RHContextGetDeviceScale(context)){
        arc_release_nil(_cachedImageRep);
        _cachedImageSize = NSZeroSize;
        _cachedImageDeviceScale = 0.0f;
    }


    //if we don't have a cached image rep, create one now
    if (!_cachedImageRep){

        //cache our cache invalidation flags
        _cachedImageSize = rect.size;
        _cachedImageDeviceScale = RHContextGetDeviceScale(context);

        //create our own NSBitmapImageRep directly because calling -[NSImage lockFocus] and then drawing an
        //image causes it to use the largest available (ie @2x) image representation, even though our current
        //contexts scale is 1 (on non HiDPI screens) meaning that we inadvertently would use @2x assets to draw for @1x contexts
        _cachedImageRep =  [[NSBitmapImageRep alloc]
                            initWithBitmapDataPlanes:NULL
                            pixelsWide:_cachedImageSize.width * _cachedImageDeviceScale
                            pixelsHigh:_cachedImageSize.height * _cachedImageDeviceScale
                            bitsPerSample:8
                            samplesPerPixel:4
                            hasAlpha:YES
                            isPlanar:NO
                            colorSpaceName:[[[self representations] lastObject] colorSpaceName]
                            bytesPerRow:0
                            bitsPerPixel:32];
        [_cachedImageRep setSize:rect.size];

        if (!_cachedImageRep){
            NSLog(@"Error: failed to create NSBitmapImageRep from rep: %@", [[self representations] lastObject]);
            return;
        }

        NSGraphicsContext *newContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:_cachedImageRep];
        if (!newContext){
            NSLog(@"Error: failed to create NSGraphicsContext from rep: %@", _cachedImageRep);
            arc_release_nil(_cachedImageRep);
            return;
        }

        [NSGraphicsContext saveGraphicsState];
        [NSGraphicsContext setCurrentContext:newContext];

        NSRect drawRect = NSMakeRect(0.0f, 0.0f, _cachedImageSize.width, _cachedImageSize.height);

        [[NSColor clearColor] setFill];
        NSRectFill(drawRect);


        BOOL shouldTile = (_resizingMode == RHResizableImageResizingModeTile);
        RHDrawNinePartImage(drawRect,
                            [_imagePieces objectAtIndex:0], [_imagePieces objectAtIndex:1], [_imagePieces objectAtIndex:2],
                            [_imagePieces objectAtIndex:3], [_imagePieces objectAtIndex:4], [_imagePieces objectAtIndex:5],
                            [_imagePieces objectAtIndex:6], [_imagePieces objectAtIndex:7], [_imagePieces objectAtIndex:8],
                            NSCompositeSourceOver, 1.0f, shouldTile);
         [NSGraphicsContext restoreGraphicsState];
     }

    //finally draw the cached image rep
    fromRect = NSMakeRect(0.0f, 0.0f, _cachedImageSize.width, _cachedImageSize.height);
    [_cachedImageRep drawInRect:rect fromRect:fromRect operation:op fraction:requestedAlpha respectFlipped:respectContextIsFlipped hints:hints];

}

Implementing NSDrawNinePartImage()

In the code above and linked below, we use the system provided NSDrawNinePartImage() by default, however in the interests of learning let’s re-implement its drawing code below. It might also be useful if you want to always stretch all parts of an image while resizing, because the NS version currently only supports tiling.

First up we need a method to draw an image tiled in a given rect. To do this we have to mostly drop down to the CoreGraphics layer and do the work there.

RHDrawTiledImageInRect

void RHDrawTiledImageInRect(NSImage* image, NSRect rect, NSCompositingOperation op, CGFloat fraction){
    CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
    CGContextSaveGState(context);

    [[NSGraphicsContext currentContext] setCompositingOperation:op];
    CGContextSetAlpha(context, fraction);

    //pass in the images actual size in points rather than rect. This gives us the actual best representation for the current context. if we passed in rect directly, we would always get the @2x representation because NSImage assumes more pixels are always better.
    NSRect outRect = NSMakeRect(0.0f, 0.0f, image.size.width, image.size.height);
    CGImageRef imageRef = [image CGImageForProposedRect:&outRect context:[NSGraphicsContext currentContext] hints:NULL];

    CGContextClipToRect(context, NSRectToCGRect(rect));
    CGContextDrawTiledImage(context, CGRectMake(rect.origin.x, rect.origin.y, image.size.width, image.size.height), imageRef);

    CGContextRestoreGState(context);
}

Next up is a quick helper function to make our code simpler.

RHDrawImageInRect

void RHDrawImageInRect(NSImage* image, NSRect rect, NSCompositingOperation op, CGFloat fraction, BOOL tile){
    if (tile){
        RHDrawTiledImageInRect(image, rect, op, fraction);
    } else {
        RHDrawStretchedImageInRect(image, rect, op, fraction);
    }
}

Finally the bulk of the drawing logic exists in the draw method.

RHDrawNinePartImage

void RHDrawNinePartImage(NSRect frame, NSImage *topLeftCorner, NSImage *topEdgeFill, NSImage *topRightCorner, NSImage *leftEdgeFill, NSImage *centerFill, NSImage *rightEdgeFill, NSImage *bottomLeftCorner, NSImage *bottomEdgeFill, NSImage *bottomRightCorner, NSCompositingOperation op, CGFloat alphaFraction, BOOL shouldTile){

    CGFloat imageWidth = frame.size.width;
    CGFloat imageHeight = frame.size.height;

    CGFloat leftCapWidth = topLeftCorner.size.width;
    CGFloat topCapHeight = topLeftCorner.size.height;
    CGFloat rightCapWidth = bottomRightCorner.size.width;
    CGFloat bottomCapHeight = bottomRightCorner.size.height;

    NSSize centerSize = NSMakeSize(imageWidth - leftCapWidth - rightCapWidth, imageHeight - topCapHeight - bottomCapHeight);

    NSRect topLeftCornerRect = NSMakeRect(0.0f, imageHeight - topCapHeight, leftCapWidth, topCapHeight);
    NSRect topEdgeFillRect = NSMakeRect(leftCapWidth, imageHeight - topCapHeight, centerSize.width, topCapHeight);
    NSRect topRightCornerRect = NSMakeRect(imageWidth - rightCapWidth, imageHeight - topCapHeight, rightCapWidth, topCapHeight);

    NSRect leftEdgeFillRect = NSMakeRect(0.0f, bottomCapHeight, leftCapWidth, centerSize.height);
    NSRect centerFillRect = NSMakeRect(leftCapWidth, bottomCapHeight, centerSize.width, centerSize.height);
    NSRect rightEdgeFillRect = NSMakeRect(imageWidth - rightCapWidth, bottomCapHeight, rightCapWidth, centerSize.height);

    NSRect bottomLeftCornerRect = NSMakeRect(0.0f, 0.0f, leftCapWidth, bottomCapHeight);
    NSRect bottomEdgeFillRect = NSMakeRect(leftCapWidth, 0.0f, centerSize.width, bottomCapHeight);
    NSRect bottomRightCornerRect = NSMakeRect(imageWidth - rightCapWidth, 0.0f, rightCapWidth, bottomCapHeight);


    RHDrawImageInRect(topLeftCorner, topLeftCornerRect, op, fraction, NO);
    RHDrawImageInRect(topEdgeFill, topEdgeFillRect, op, fraction, shouldTile);
    RHDrawImageInRect(topRightCorner, topRightCornerRect, op, fraction, NO);

    RHDrawImageInRect(leftEdgeFill, leftEdgeFillRect, op, fraction, shouldTile);
    RHDrawImageInRect(centerFill, centerFillRect, op, fraction, shouldTile);
    RHDrawImageInRect(rightEdgeFill, rightEdgeFillRect, op, fraction, shouldTile);

    RHDrawImageInRect(bottomLeftCorner, bottomLeftCornerRect, op, fraction, NO);
    RHDrawImageInRect(bottomEdgeFill, bottomEdgeFillRect, op, fraction, shouldTile);
    RHDrawImageInRect(bottomRightCorner, bottomRightCornerRect, op, fraction, NO);

}

Lastly We Add a Category on NSImage

The final step in adding resizableImage methods to NSImage is a simple passthrough category to return new RHResizableImage instances.

@implementation NSImage (RHResizableImageAdditions)
...
-(RHResizableImage*)resizableImageWithCapInsets:(RHEdgeInsets)capInsets{
    RHResizableImage *new = [[RHResizableImage alloc] initWithImage:self capInsets:capInsets];
    return arc_autorelease(new);
}
...
@end

Wrapping Up

Phew, that was a bunch of code! But fear not, sources are available in my RHAdditions collection on GitHub.

You might also find my latest app Nine Parts useful. It makes it easy to preview and slice resizable images for iOS, OS X and the web.

As always, any and all feedback is greatly appreciated.

Follow me on Twitter @heardrwt.

RHIntervalTree

An Objective-C implementation of a Centred Interval Tree.

RHIntervalTree provides an Objective-C wrapper around an internal C++ Interval Tree implementation by Erik Garrison.

Overview

An interval tree can be used to efficiently find a set of numeric intervals overlapping or containing another interval, for example a view containing overlapping calendar events for a given day or week.

They can also be used for windowing queries, for instance, to find all roads on a computerised map inside a rectangular viewport, or to find all visible elements inside a three-dimensional scene.

See Wikipedia for more info.

Grab It Now

You can find RHIntervalTree over on GitHub.

RHAdditions

I finally got around to posting a bunch of my Objective-C categories and additions that have served me well over the years. Hopefully then can serve you too!

(useful && included) things

  • UIColor interpolation.
  • UIImage pixel access / comparison logic.
  • UIImage resizing.
  • NSThread performBlock methods.
  • Launch At Login via SMLoginItemSetEnabled().
  • NSImage -> (PNG / JPEG / GIF) representations.
  • UIView / NSView snapshotting.
  • NSString URL encoding.
  • Support building both with and without ARC.
  • NSWindow smooth resizing.
  • NSObject class-dump like logging.
  • UIView completed action badge.
  • Debug logging macros.
  • ROT13 (Yea I know!).
  • and much more…

You can find RHAdditions over on GitHub.

RHStatusItemView

Do you every find yourself wishing you could right click on an apps status icon? I sure do! (I’m looking at you Skitch, and you Twitterific.) Strangely enough while adding a menu to an NSStatusItem is easy, making it show on a right click is not!

Introducing RHStatusItemView.

An NSStatusItem view that supports handling both left and right click actions, menus and showing an image / alternateImage pair.

You can find RHStatusItemView over on GitHub.

RHColorCoordinatedTableView

Have you ever tried to change the backgroundColor of UITableView from its default white, only to realise (obviously late in the game) that its gone and changed the colour of all your tableview cells as well?

This has certainly bitten me in the past, however fear no more because as of today a new handy dandy UITableView subclass exits that can help!

It does this by adding both a preHeader view and a postFooter view and automagicly keeping them perfectly in sync with all of your wonderful tableView content no mater what you throw at it!

Say goodbye to ugly white and get your colour coordinating on!

How’s it do that? GitHub what now? Just show me!

A UITableView subclass that provides preHeader and postFooter views positioned outside of the tableViews contentOffset allowing for custom header and footer background colours.

You can find RHColorCoordinatedTableView over on GitHub.

Creating Static iOS Libraries

This is part 2 of a 2 part series on using and creating static iOS libraries. See part one: Using static iOS Libraries. for info on how to use static libraries in your own projects.

All of the below steps can be applied to create and package your own static libraries for distribution.

General Procedure

  • Create a new Xcode project of the iOS > “Cocoa Touch Static Library” variety.
  • Add your relevant source files to the project. (.h & .m)
  • I like to add a single .h file that includes all other public headers e.g. <staticLibName>.h
  • Select your newly created target.
  • Select “Build Phases”.
  • Delete the Auto Generated “Copy Files” Phase. (Xcode defaults to adding this, however i prefer to use an actual copy headers phase, which we will add in a second.)
    image
  • Now add a new “Copy Headers” phase.
  • Add your public headers to the public section & your private headers to the private section.
    image
  • Add any other dependant frameworks to the “Link Binary With Libraries” phase. (e.g. Addressbook.framework or CoreLocation.framework)
  • Select “Build Settings”.
  • Find Public Headers Folder Path and set it to include/$(TARGET_NAME) we also want to set Private Headers Folder Path to $(PUBLIC_HEADERS_FOLDER_PATH)/Private. This allows users of our library to find and include our headers. (We include the $(TARGET_NAME) variable so that users can include our headers using the standard framework idiom #include <folder/header.h>.)

  • Find Installation Directory and set it to /.
  • Find Skip Install and set it to Yes.
  • Make sure -all_load -ObjC is added to the Other Linker Flags section. (This makes sure everything in the static library is linked, even if not used at compile time. i.e. making it available at run time.)

Other Things To Consider

Trouble Finding Headers

Your users may have trouble finding headers in their projects even though they have added include/ to their Header Search Paths.They are likely running into an issue whereby the static library is being built with one configuration name and their project is building with another. (i.e. Debug vs DebugStaging)

Xcode will, on finding a matching configuration name in a sub-project use that configuration to build the sub-projects targets, however if it can’t find a matching configuration it uses the projects default config. This can lead to problems finding the matching headers. (i.e. Xcode is looking in Build/Products/DebugStaging-iphonesimulator and your library is being built into Build/Products/Debug-iphonesimulator)

To work around this you can set your public / private headers folder paths to be ../../Headers/$(TARGET_NAME), i.e. in the folder above the config specific folder that Xcode creates when building projects. If you choose to do this your users will instead need to add this folder to their header search paths instead of include/.

Headers being included in Xcode archives

If the path your static library is putting its headers into starts with a slash i.e. /include/$(TARGET_NAME), Xcode will include the headers in archived builds (because it’s a fully rooted path). This leads to app store validation failing when trying to upload a build. The solution to this problem is to change your public headers folder path in your library to no longer include the /.

Ideally you would set Public Headers Folder Path to include/$(TARGET_NAME) and Private Headers Folder Path to $(PUBLIC_HEADERS_FOLDER_PATH)/Private.

Specific Build Settings

Your Static Library Project

PUBLIC_HEADERS_FOLDER_PATH="include/$(TARGET_NAME)"; /*Public Headers Folder Path*/
PRIVATE_HEADERS_FOLDER_PATH="$(PUBLIC_HEADERS_FOLDER_PATH)/Private"; /*Private Headers Folder Path*/

Users Projects

HEADER_SEARCH_PATHS="include/**"; /*Header Search Paths (the ** indicates recursive)*/
Tagged , ,

Using Static iOS Libraries

This is part 1 of a 2 part series on using and creating static iOS libraries. See part two: Creating static iOS Libraries. for info on how to create your own static libraries for distribution.

All of the below steps can be applied generally to include any static library in your current iOS project.

General Procedure

  • Fork & checkout the static library into a sub folder of your project. (I like to create a “Third Party” folder & Xcode group.)
mkdir -p ~/Desktop/StaticLibraryDemo/"Third Party"
cd ~/Desktop/StaticLibraryDemo/"Third Party"
git clone git://github.com/heardrwt/RHAddressBook.git
  • Drag the static libraries Xcode project into your applications Xcode project.
    image
  • Select your applications current target.
  • Select “Build Phases”.
  • Add <StaticLibName> to your targets “Target Dependencies” section.
  • Add lib<staticLibName>.a to the “Link Binary With Libraries” section.
    image
  • Link any other dependant frameworks. (e.g. Addressbook.framework or CoreLocation.framework)
  • Select “Build Settings”.
  • Find Header Search Paths and add an entry consisting of include/. (The library may be set up to use another path. If this is the case, see the “Trouble Finding Headers” section below.) (I usually select the recursive option.)
    image
  • Make sure -all_load -ObjC is added to the Other Linker Flags section. (This makes sure everything in the static library is linked, even if not used at compile time. i.e. making it available at run time.)
    image
  • Finally include the static library headers in your source files using #import <RHAddressBook/AddressBook.h>.

Handy Tips

Trouble Finding Headers

If you are having trouble finding headers in your project even though you have added include/ to your Header Search Paths you may be running into an issue whereby the static library is being built with one configuration name and your project is building with another. (i.e. Debug vs DebugStaging)

Xcode will, on finding a matching configuration name in a sub-project use that configuration to build the sub-projects targets, however if it can’t find a matching configuration it uses the projects default config. This can lead to problems finding the matching headers.

Some static libs work around this by setting their public / private headers folder paths to be ../../Headers/$(TARGET_NAME), i.e. in the folder above the config specific folder that Xcode creates when building projects. If this is the case you will need to add this path to your header search paths instead of include/.

Another way to work around this is to use the direct path to the .h files inside your Third Party folder, i.e. set Header Search Paths to ""$(PROJECT_DIR)/Third Party" making sure to select recursive.

Finally, my preferred way depending on the project is to actually modify the static libraries project (You did fork the framework, didn’t you?), adding extra configurations that match the parent projects configuration names.

Headers being included in Xcode archives

If the path the static library is putting its headers into starts with a slash i.e. /include/$(TARGET_NAME), Xcode will include the headers in archived builds (because it’s a fully rooted path). This leads to app store validation failing when trying to upload a build. The solution to this problem is to change the public headers folder path in the static lib to no longer include the /.

Ideally you would set Public Headers Folder Path to include/$(TARGET_NAME) and Private Headers Folder Path to $(PUBLIC_HEADERS_FOLDER_PATH)/Private.

Specific Build Settings

Your Project

HEADER_SEARCH_PATHS="include/**"; /*Header Search Paths (the ** indicates recursive)*/

Static Library Project

PUBLIC_HEADERS_FOLDER_PATH="include/$(TARGET_NAME)"; /*Public Headers Folder Path*/
PRIVATE_HEADERS_FOLDER_PATH="$(PUBLIC_HEADERS_FOLDER_PATH)/Private"; /*Private Headers Folder Path*/
Tagged ,

ARC Compatibility Macros

If you ever find yourself writing code that needs to support being compiled with ARC (Automatic Reference Counting) both enabled and disabled this will likely be useful for you.

Firstly however, unless you have a good reason as to why your code needs to be compatible with ARC enabled and disabled, its probably a better idea  to just pick one and use the ARC enabling / disabling compiler flags for the particular files in question.  (-fobjc-arc / -fno-objc-arc)

Some places where it might however be easier in the long run include re-usable categories and classes that people will likely be including in their projects. ie open source stuff.

These are the macros that i used when adding ARC support to RHAddressBook a while back. Hopefully they are useful to others.

ARC Compatibility Macros — Gist.

RHFirstUserExperience

A comprehensive first user experience manager for your next iOS project.

It supports custom overlay views loaded from classes and Nib files at runtime based on a plist of defined actions. Keeps track of display limits, allowing for true first user experience.

The views are decoupled from their associated view controllers, allowing for more flexible and more reliable code re-use.

Check it out over on GitHub.

BringBackDelete Safari 6.0 extension

A Simple Safari 6.0+ extension that re-enables “back to previous page” navigation when you press the delete key.

  • It won’t steal focus from text / input fields.
  • It won’t steal focus from flash plugins etc.

This functionality was removed in Safari 6.0 which made me sad. Hopefully someone else also finds this useful.

Find it on GitHub

Tagged , , , ,

It’s nearly IPv6 time!

It’s nearly IPv6 time! Super excited.

Tagged ,

iPhone Mockup Template

Jenny and i put together a simple iPhone Mockup template the other day.

It has 3 iPhone views per page (A4 or US Letter), space for notes and a title for each view.

I figured others might find it useful, so i might as well share it.

iPhone Mockup Template

 

RHPreferences

A simple and easy Preferences window controller with multiple tabs for your next Mac application.

A Mac OS X window controller subclass that makes it easy to provide a standard Preferences window with multiple tabs in your application.

It also provides:

  • Auto resizing between different sized tab views (With animation)
  • Custom NSToolbarItem support
  • Persistance of the last used tab
  • Support for placeholder NSToolbarItems (eg NSToolbarFlexibleSpaceItemIdentifier & NSToolbarShowFontsItemIdentifier)

Check it out over on GitHub.

Pan Seared Halibut

20120604-025321.jpg

Pan seared Californian Halibut with Israeli couscous and a fresh summer citrus reduction.

Preparing the Citrus

20120604-022839.jpg

Preparing the Citrus.

RHKeychain

Cocoa wrapper functions to the Mac OS X keychain to create, edit and delete generic password items. 

I’ve been working on a Mac app recently and found myself having to store some passwords etc to the Keychain.

The Security.framework interface isn’t exactly user friendly so i put together some simple cocoa wrapper functions that allow for storing arbitrary keys and values to the Mac OS X Keychain. ( Specifically to create, edit and delete generic password items.)

RHKeychain is available over on my GitHub Profile.

Let me know if you find any issues, or find them useful.

Potato Gnocchi

20120604-032842.jpg

Potato Gnocchi.

RHAddressbook for iOS

A Cocoa / Objective-C library for interfacing with the iOS AddressBook that also adds geocoding support.

Recently i had some time and put together an Objective-C AddressBook wrapper framework for iOS. It allows you to access to entirety of the AddressBook.framework without having to do any bridging back and forth from CoreFoundation.

I would love to hear any feedback.

Check it out over at GitHub.

 

RHHorizontalScroll

I was recently exploring a navigation paradigm for iOS that replicates an experience more familiar to those of you using Android. Namely swiping between multiple independent navigation stacks at a particular level in your app.

Now this is currently little more than an experiment so you have been warned.

The sample project itself explores creating an application that allows swipes between 3 main navigations stacks without use of a TabBar. It also shows a floating overlay bar that gives an indication of the users position in swipe stack.

Edit: Renamed, static library added and link updated.

Check it out on GitHub. ( RHHorizontalScroll )