Method Swizzling in Objective-C

There are many reasons for you to use method swizzling, even if it’s considered harmful and dangerous.

Despite it’s flaws, this little technique can be so powerful and let you reveal secrets of iOS like status bar is drawn by the current app, and replace methods in class you don’t own like customizing UINavigationBar background in old days to achieve a higher level of UI customisation.

So here’s the base snippet:

Example usage:

@implementation NSParagraphStyle (JTParagraphStyleDebug)

+ (void)load {
    SwizzleInstanceMethod([self class], @selector(initWithCoder:), @selector(initWithCoderSwizzled:));
}

// -[NSParagraphStyle initWithCoder:] is called when an NSAttributedString is specified in an UILabel in Interface Builder.
- (id)initWithCoderSwizzled:(NSCoder *)aDecoder {
    self = [self initWithCoderSwizzled:(id)[[UINibDecoderProxy alloc] initWithTarget:aDecoder]];
    return self;
}

@end

Above code is extracted from JTAttributedLabel to use UINibDecoderProxy to diagnose NSAttributedString in Interface Builder.

Enjoy and have a good use with it!

Quickly switch supported UIInterfaceOrientation for your View Controllers

In Xcode 4, you can simply point-and-click to specify supported orientation in your info.plist. But wait, it doesn’t really make your app obey to that certain interface orientation.

You still have to implement the - [UIViewController shouldAutorotateToInterfaceOrientation:] and return a right value.

Your specified value is located in your app’s info.plist, so here’s the code to let you easily access that configuration.

Example usage

// In your ViewController.m subclass that you wish to just obey the specified supported orientations,
// #import "UIApplicationAddition.h"
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    return UIInterfaceOrientationIsSupportedOrientation(interfaceOrientation);
}

Now you can just point-and-click to really change the supported orientations!

Adding Block support for UIControl’s Target-Action mechanism

Tired of Target-Action? Welcome to the world of blocks.

Sample usage:

UIButton *button = ...;
[button addEventHandler:^(id sender, UIEvent *event) {
    NSLog(@"touchedUp!");
} forControlEvent:UIControlEventTouchUpInside];

This is a very simple implementation of adding block support for UIControls. Download the snippet from here, instantly saves you a few lines of code.

If you are finding for a more complex block addition of everything, you maybe interested to have a look at zwaldowski / BlocksKit.

Splitting an array to several components

Say you’d like to split an array into several arrays each with a specific number of components.

// Say your originalArray is [@"A", @"B", @"C", @"D"]
NSArray *originalArray = [NSArray arrayWithObjects:@"A", @"B", @"C", @"D", nil];

// And we now want the array to be split into two arrays with two per segment
// like this [[@"A", @"B"], [@"C", @"D"]]
NSArray *newArray = [NSArray splitArray:originalArray componentsPerSegment:1];


And here’s the code snippet on how you can do it.

//
//  NSArray+JTArraySplit.h
//
//  http://ioscodesnippet.tumblr.com
//

#import <Foundation/Foundation.h>

@interface NSArray (JTArraySplit)

+ (NSArray *)splitArray:(NSArray *)targetArray componentsPerSegment:(NSUInteger)componentsCount;

@end


//
//  NSArray+JTArraySplit.m
//
//  http://ioscodesnippet.tumblr.com
//

#import "NSArray+JTArraySplit.h"

@implementation NSArray (JTArraySplit)

+ (NSArray *)splitArray:(NSArray *)targetArray componentsPerSegment:(NSUInteger)componentsCount {
    NSMutableArray *splitedArray = [NSMutableArray array];

    NSUInteger targetArrayCount = [targetArray count];

    if (targetArrayCount > 0) {
        int index = 0;
        while (index < targetArrayCount) {
            int length = MIN(targetArrayCount - index, componentsCount);
            NSArray *subArray = [targetArray subarrayWithRange:NSMakeRange(index, length)];
            [splitedArray addObject:subArray];
            index = index+length;
        }
        return splitedArray;
    } else {
        // no objects inside targetArray, so just return empty array
        return splitedArray;
    }
}

@end
Force decompressing UIImage in background to achieve better performance

If you’ve ever experience in loading lots of image in your app from the web, and display in a list form of UIImages in a table view, you’d properly heard of doing lazy loading those images. There are several great loading and caching open source solutions you’d probably already heard of such as SDWebImage, EGOImageLoading, etc.

However, you are still experience slight UI delay when the image finished loading or caching out from the disk. The reason behind is UIKit does extra lazy initialization, and only do expensive decompressing at the time to display or draw.

Here’s is code snippet meant to be load from a background thread that force an image to be decompressed into the right format, so that the system don’t have to do extra conversion on display.

//
//  UIImage+JTImageDecode.h
//
//  Created by james on 9/28/11.
//  http://ioscodesnippet.tumblr.com
//

@interface UIImage (JTImageDecode)
+ (UIImage *)decodedImageWithImage:(UIImage *)image;
@end



//
//  UIImage+JTImageDecode.m
//
//  Created by james on 9/28/11.
//  http://ioscodesnippet.tumblr.com
//

@implementation UIImage (JTImageDecode)

+ (UIImage *)decodedImageWithImage:(UIImage *)image {
    CGImageRef imageRef = image.CGImage;
    // System only supports RGB, set explicitly and prevent context error
    // if the downloaded image is not the supported format
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    CGContextRef context = CGBitmapContextCreate(NULL,
                                                 CGImageGetWidth(imageRef),
                                                 CGImageGetHeight(imageRef),
                                                 8,
                                                 // width * 4 will be enough because are in ARGB format, don't read from the image
                                                 CGImageGetWidth(imageRef) * 4,
                                                 colorSpace,
                                                 // kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little 
                                                 // makes system don't need to do extra conversion when displayed.
                                                 kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); 
    CGColorSpaceRelease(colorSpace);

    if ( ! context) {
        return nil;
    }
    
    CGRect rect = (CGRect){CGPointZero, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)};
    CGContextDrawImage(context, rect, imageRef);
    CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context);
    CGContextRelease(context);
    
    UIImage *decompressedImage = [[UIImage alloc] initWithCGImage:decompressedImageRef];
    CGImageRelease(decompressedImageRef);
    return [decompressedImage autorelease];
}

@end 

So after an image has successfully loaded from the web or cached out, create an operation and decompress the image with 

[UIImage decodedImageWithImage:anImage];

And now you can achieve a smoother scrolling experience for your app.

You can also download UIImage+JTImageDecode directly on gist or checkout the optimized mystcolor/SDWebImage library on github.

Adding drop shadow on UINavigationBar

Somehow adding drop shadows on UINavigationBar using the CALayer property fails for me but I later find it out we just need a little trick there.

// The magic is to have -[UIView clipToBounds] set to NO
self.navigationController.navigationBar.clipsToBounds = NO;



Base on the fact this is ioscodesnippet, I know I have to make a really simple UINavigationBar+JTDropShadow category to make our life more easier.

//
//  UINavigationBar+JTDropShadow.h
//
//  Created by james on 9/20/11.
//  http://ioscodesnippet.tumblr.com
//


#import 

@interface UINavigationBar (JTDropShadow)

- (void)dropShadowWithOffset:(CGSize)offset
                      radius:(CGFloat)radius
                       color:(UIColor *)color
                     opacity:(CGFloat)opacity;


@end




//
//  UINavigationBar+JTDropShadow.m
//
//  Created by james on 9/20/11.
//  http://ioscodesnippet.tumblr.com
//


#import "UINavigationBar+JTDropShadow.h"

#import 



@implementation UINavigationBar (JTDropShadow)

- (void)dropShadowWithOffset:(CGSize)offset
                      radius:(CGFloat)radius
                       color:(UIColor *)color 
                     opacity:(CGFloat)opacity {

    // Creating shadow path for better performance
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, self.bounds);
    self.layer.shadowPath = path;
    CGPathCloseSubpath(path);
    CGPathRelease(path);

    self.layer.shadowColor = color.CGColor;
    self.layer.shadowOffset = offset;
    self.layer.shadowRadius = radius;
    self.layer.shadowOpacity = opacity;

    // Default clipsToBounds is YES, will clip off the shadow, so we disable it.
    self.clipsToBounds = NO;

}

@end



Usage:


- (void)viewDidLoad {
    [super viewDidLoad];
    ...
    [self.navigationController.navigationBar dropShadowWithOffset:CGSizeMake(0, 3)
                                                           radius:1
                                                            color:[UIColor darkGrayColor]
                                                          opacity:1];
    ...
}



Don’t forget to import QuartzCore.framework in your build settings.

While it is more generic by making it a UIView category, but I’ll leave it simple here to demonstrate the main purpose.

How about a screenshot this time? ;P

Creating an image of image in specific rect or by proportion
CGImageCreateWithImageInRect

That’s already defined in the Foundation framework. It is absolute fine for who already familiar well enough with CoreGraphics and doesn’t mind manually take care of the image orientation.

We loved UIKit anyway.

Use this UIImage+JTImageCrop category instead.

//
//  UIImage+JTImageCrop.h
//
//  Created by james on 9/8/11.
//  http://ioscodesnippet.tumblr.com
//

#import <UIKit/UIKit.h>


@interface UIImage (JTImageCrop)

+ (UIImage *)imageWithImage:(UIImage *)image cropInRect:(CGRect)rect;

// define rect in proportional to the target image.   
//
//  +--+--+
//  |A | B|
//  +--+--+
//  |C | D|
//  +--+--+
//
//  rect {0, 0, 1, 1} produce full image without cropping.
//  rect {0.5, 0.5, 0.5, 0.5} produce part D, etc.

+ (UIImage *)imageWithImage:(UIImage *)image cropInRelativeRect:(CGRect)rect;

@end

// Used by +[UIImage imageWithImage:cropInRelativeRect]
CGRect CGRectTransformToRect(CGRect fromRect, CGRect toRect);

//
//  UIImage+JTImageCrop.m
//
//  Created by james on 9/8/11.
//  http://ioscodesnippet.tumblr.com
//

#import "UIImage+JTImageCrop.h"


CGRect CGRectTransformToRect(CGRect fromRect, CGRect toRect) {
    CGPoint actualOrigin = (CGPoint){fromRect.origin.x * CGRectGetWidth(toRect), fromRect.origin.y * CGRectGetHeight(toRect)};
    CGSize  actualSize   = (CGSize){fromRect.size.width * CGRectGetWidth(toRect), fromRect.size.height * CGRectGetHeight(toRect)};
    return (CGRect){actualOrigin, actualSize};
}

@implementation UIImage (JTImageCrop)

+ (UIImage *)imageWithImage:(UIImage *)image cropInRect:(CGRect)rect {
    NSParameterAssert(image != nil);
    if (CGPointEqualToPoint(CGPointZero, rect.origin) && CGSizeEqualToSize(rect.size, image.size)) {
        return image;
    }

    UIGraphicsBeginImageContextWithOptions(rect.size, NO, 1);
    [image drawAtPoint:(CGPoint){-rect.origin.x, -rect.origin.y}];
    UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return croppedImage;
}

+ (UIImage *)imageWithImage:(UIImage *)image cropInRelativeRect:(CGRect)rect {
    NSParameterAssert(image != nil);
    if (CGRectEqualToRect(rect, CGRectMake(0, 0, 1, 1))) {
        return image;
    }
    
    CGRect imageRect = (CGRect){CGPointZero, image.size};
    CGRect actualRect = CGRectTransformToRect(rect, imageRect);
    return [UIImage imageWithImage:image cropInRect:CGRectIntegral(actualRect)];
}

Now you use this instead of the CoreGraphics method.

+ (UIImage *)imageWithImage:(UIImage *)image cropInRect:(CGRect)rect;

Last but not least, you’d somehow want to further abstract it with a proportional rect value. (Imagine you are defining a cropping area on the screen and want to crop a full sized image, you’d transform the visible area to the full sized photo).

A handly method is also created for this purpose.

+ (UIImage *)imageWithImage:(UIImage *)image cropInRelativeRect:(CGRect)rect;


While you may be interested in more of it, go visit JTImageKit on GitHub! Documentations are raw right now, but welcome any comments!

Adding fadeout effect to any -[UIViews removeFromSuperview]

Typically you like to do something like below when you wanted to remove a view from its parent view.

[myView removeFromSuperview];


Sometimes it’s not that please for a user to see an UI component disappearing suddenly. You’d then consider adding some transition effects, and here’s a little code snippet in the UIView+JTRemoveAnimated category for how you can get a fade out effect on view removal.


So from now on, you just needed to do this to fade out any UIViews

[myView removeFromSuperviewAnimated]