[Objective-C + Cocoa] Screen Capture, Multiplexing, and More

It’s been a bit quiet on the coding-front lately, so I thought I’d share a small handful of various utilities that I’ve developed over the past few weeks. None of them are particularly revolutionary, but they can certainly prove useful in the right situations. Anyways, in no particular order, I give you:

ScreenCaptureView

This bit of code allows you to easily capture the contents of any arbitrary UIView:

//  ScreenCaptureView.h
#import <UIKit/UIKit.h>

@interface ScreenCaptureView : UIView {
}

@property(retain) UIImage* currentScreen;
@property(assign) float frameRate;

@end


//  ScreenCaptureView.m
#import "ScreenCaptureView.h"
#import <QuartzCore/QuartzCore.h>

@implementation ScreenCaptureView

@synthesize currentScreen, frameRate;

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        self.contentMode = UIViewContentModeRedraw;
        self.frameRate = 20.0f;//20 frames per seconds
    }
    return self;
}

- (void) drawRect:(CGRect)rect {
    CGContextRef context =  UIGraphicsGetCurrentContext();
    [self.layer renderInContext:context];
    CGImageRef cgImage = CGBitmapContextCreateImage(context);
    UIImage* background = [UIImage imageWithCGImage: cgImage];
    CGImageRelease(cgImage);
    self.currentScreen = background;
    
    [self performSelector:@selector(setNeedsDisplay) withObject:nil afterDelay:(1.0 / self.frameRate)];   //redraw at the specified framerate
}

- (void)dealloc {
    [super dealloc];
}
@end

The ‘ScreenCaptureView‘ class automatically captures its current contents (including any nested subviews) to a UIImage which it exposes through the ‘currentScreen‘ property. You can use this functionality to do things like generate screenshots of your application for use in help/tutorial (or promotional) content, and you can even pass the data as frames to an `AVCaptureSession` to create a live video of your app in action.

Update: An enhanced version of this class that includes the ability to record video is now available.

MultiplexingDelegate

For those rare cases when you want multiple delegate objects to be notified of a change/event in some object:

//  MultiplexingDelegate.h
#import <Foundation/Foundation.h>

@interface MultiplexingDelegate : NSObject {
    NSMutableArray* delegates;
}

- (void) addDelegate: (id) theDelegate;
- (void) removeDelegate: (id) theDelegate;
- (void) sendSelectorToDelegates: (SEL)selector withObject: (id)param1 andSecondObject: (id)param2
@end

//  MultiplexingDelegate.m
@implementation MultiplexingDelegate
- (id) init {
    if ((self = [super init])) {
        delegates = [[NSMutableArray alloc] initWithCapacity: 16];
    }
}

- (void) dealloc {
    [delegates release];
    [super dealloc]
}

- (void) addDelegate: (id) theDelegate {
    @synchronized(delegates) {
        if (theDelegate && ! [delegates containsObject: theDelegate]) {
            [delegates addObject: theDelegate];
        }
    }
}

- (void) removeDelegate: (id) theDelegate {
    @synchronized(delegates) {
        if (theDelegate && [delegates containsObject: theDelegate]) {
            [delegates removeObject: theDelegate];
        }
    }
}

- (void) sendSelectorToDelegates: (SEL)selector withObject: (id)param1 andSecondObject: (id)param2 {
    @synchronized(delegates) {
        for (id theDelegate in delegates) {
            if (param2) {
                [theDelegate performSelector:selector withObject:param1 withObject:param2];
            }
            else if (param1) {
                [theDelegate performSelector:selector withObject:param1];
            }
            else {
                [theDelegate performSelector:selector];
            }
        }
    }
}
@end

The ‘MultiplexingDelegate` class provides a generic starting point for creating delegates that conform to various protocols and support the multiplexing of events to any number of attached listeners. To use it you just need to create a new subclass of ‘MultiplexingDelegate‘ that implements whatever protocol you are interested in. For instance, here is a partial example of a multiplexing UISearchBarDelegate implementation:

//  MultiplexingSearchBarDelegate.h
#import <UIKit/UIKit.h>
#import "MultiplexingDelegate.h"

@interface MultiplexingSearchBarDelegate : MultiplexingDelegate<UISearchBarDelegate> {
}
@end

//  MultiplexingSearchBarDelegate.m
@implementation MultiplexingSearchBarDelegate
//add UISearchBarDelegate methods here, following a pattern like this:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
    [self sendSelectorToDelegates: @selector(searchBar:textDidChange:) withObject: searchBar andSecondObject: searchText];
}

//or alternately/equivalently, a pattern like this:
- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    @synchronized(delegates) {
        for (id<UISearchBarDelegate> theDelegate in delegates) {
            [theDelegate searchBar:searchBar shouldChangeTextInRange: range replacementText: text];
        }
    }
}
@end

To use the ‘MultiplexingSearchBarDelegate‘ class you would create a single instance and add it as the delegate of your UISearchBar. Then you just register every other object that you want to receive UISearchBar events with the ‘MultiplexingSearchBarDelegate‘ (by using the ‘addDelegate:‘ method), and they will all receive event notifications from the UISearchBar. Simple, but powerful.

NSString+JavaAPI

There’s no getting around it, the standard NSString API just kind of sucks. Wouldn’t it be great if NSString supported an API that was more like what Java provides through its String class? Well now it can:

//  NSString+JavaAPI.h
#import <Foundation/Foundation.h>

@interface NSString (NSString_JavaAPI)
- (int) compareTo: (NSString*) comp;
- (int) compareToIgnoreCase: (NSString*) comp;
- (bool) contains: (NSString*) substring;
- (bool) endsWith: (NSString*) substring;
- (bool) startsWith: (NSString*) substring;
- (int) indexOf: (NSString*) substring;
- (int) indexOf:(NSString *)substring startingFrom: (int) index;
- (int) lastIndexOf: (NSString*) substring;
- (int) lastIndexOf:(NSString *)substring startingFrom: (int) index;
- (NSString*) substringFromIndex:(int)from toIndex: (int) to;
- (NSString*) trim;
- (NSArray*) split: (NSString*) token;
- (NSString*) replace: (NSString*) target withString: (NSString*) replacement;
- (NSArray*) split: (NSString*) token limit: (int) maxResults;
@end

//  NSString+JavaAPI.m
#import "NSString+JavaAPI.h"

@implementation NSString (NSString_JavaAPI)
- (int) compareTo: (NSString*) comp {
    NSComparisonResult result = [self compare:comp];
    if (result == NSOrderedSame) {
        return 0;
    }
    return result == NSOrderedAscending ? -1 : 1;
}

- (int) compareToIgnoreCase: (NSString*) comp {
    return [[self lowercaseString] compareTo:[comp lowercaseString]];
}

- (bool) contains: (NSString*) substring {
    NSRange range = [self rangeOfString:substring];
    return range.location != NSNotFound;
}

- (bool) endsWith: (NSString*) substring {
    NSRange range = [self rangeOfString:substring];
    return range.location == [self length] - [substring length];
}

- (bool) startsWith: (NSString*) substring {
    NSRange range = [self rangeOfString:substring];
    return range.location == 0;
}

- (int) indexOf: (NSString*) substring {
    NSRange range = [self rangeOfString:substring];
    return range.location == NSNotFound ? -1 : range.location;
}

- (int) indexOf:(NSString *)substring startingFrom: (int) index {
    NSString* test = [self substringFromIndex:index];
    return [test indexOf:substring];
}

- (int) lastIndexOf: (NSString*) substring {
    if (! [self contains:substring]) {
        return -1;
    }
    int matchIndex = 0;
    NSString* test = self;
    while ([test contains:substring]) {
        if (matchIndex > 0) {
            matchIndex += [substring length];
        }
        matchIndex += [test indexOf:substring];
        test = [test substringFromIndex: [test indexOf:substring] + [substring length]];
    }
    
    return matchIndex;
}

- (int) lastIndexOf:(NSString *)substring startingFrom: (int) index {
    NSString* test = [self substringFromIndex:index];
    return [test lastIndexOf:substring];
}

- (NSString*) substringFromIndex:(int)from toIndex: (int) to {
    NSRange range;
    range.location = from;
    range.length = to - from;
    return [self substringWithRange: range];
}

- (NSString*) trim {
    return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}

- (NSArray*) split: (NSString*) token {
    return [self split:token limit:0];
}

- (NSArray*) split: (NSString*) token limit: (int) maxResults {
    NSMutableArray* result = [NSMutableArray arrayWithCapacity: 8];
    NSString* buffer = self;
    while ([buffer contains:token]) {
        if (maxResults > 0 && [result count] == maxResults - 1) {
            break;
        }
        int matchIndex = [buffer indexOf:token];
        NSString* nextPart = [buffer substringFromIndex:0 toIndex:matchIndex];
        buffer = [buffer substringFromIndex:matchIndex + [token length]];
        if (nextPart) {
            [result addObject:nextPart];
        }
    }
    if ([buffer length] > 0) {
        [result addObject:buffer];
    }
    
    return result;
}

- (NSString*) replace: (NSString*) target withString: (NSString*) replacement {
    return [self stringByReplacingOccurrencesOfString:target withString:replacement];
}
@end

Note that this isn’t the complete Java String API (and also that `split()` does not support regular-expressions as it does in Java). It is just a subset consisting of the most useful Java API functions that do not have convenient analogs in the standard NSString API. Still, it makes a lot of common string manipulations a lot more straightforward than they otherwise would be.

This entry was posted in coding, objective-c and tagged , , , . Bookmark the permalink.

19 Responses to [Objective-C + Cocoa] Screen Capture, Multiplexing, and More

  1. Marjorie Kirch says:

    I heard the news speak about getting grants for web development purposes so I have been trying to build the perfect website to get one. Can this code help me?

  2. bt says:

    I want to say – thank you for this

  3. aussi says:

    Thank you for spending the time posting this code. You mention for the first example that “you can even pass the data as frames to an `AVCaptureSession` to create a live video of your app in action”. Do you have any pointers on how to do this – Presumably this would involve writing a concrete subclass of AVCaptureInput that accepts UIImages, then setting up an AVCaptureSession using this, but I’m a bit stuck on where to start with this! Any help would be very much appreciated.

  4. Keko says:

    Found a bug on lastindexof, correct code is

    – (int) lastIndexOf: (NSString*) substring {
    int matchIndex = -1;
    int count = -1;
    NSString* test = self;
    while ([test contains:substring]) {
    matchIndex = [test indexOf:substring];
    count += matchIndex + 1;
    NSLog(@”%d”, matchIndex);
    test = [test substringFromIndex: matchIndex + 1];
    }

    return count;
    }

  5. Anil says:

    Many thanks for the tutorial.

    Any suggestions as to how I can enhance the screen capture program to capture audio from the device as well?

  6. pavan itagi says:

    heyy … i am just adding a buttons in a UIViewController class. and allocated a screencaptureview class and added it to controllers subview.

    but after recording is done . i am just getting black screen in video even if my view controllers view is some other color.

    so please do help me i think i am adding it in a wrong way to viewcontroller

    thanks

  7. sajjan says:

    Hi,
    it can’t capture landscape screen properly.how can i fix it?

  8. Vikas Gupta says:

    Hi Aroth,

    Can we record screen of MPMoviePlayerController. If it can done by this project then how i can achieve this. because when i m using ScreenCaptureView class then i got only black screen video.

  9. Chinnasamy C says:

    I am playing the video on subview on UIView, I have set the ScreencaptureView in root UIView.

    It will record only a which screen of root view not video playing sub view, Can any one help to to solve this problem.

    Also can you upgrade this library to Support ARC ?

    Thanks,
    Chinna

  10. Delila says:

    I read a lot of interesting articles here. Probably you spend
    a lot of time writing, i know how to save you a
    lot of time, there is an online tool that creates readable, SEO friendly articles in minutes, just type
    in google – laranitas free content source

  11. daksh says:

    thanks for sharing

  12. Pooja says:

    I hope, you will share these types of posts in future. Awesome Mate.

  13. Tip, dont use spells on buildings… except when you are sure your spell would destroy it?

  14. The the very next time I read a blog, I hope so it doesnt disappoint me as much as this. Come on, man, It was my solution to read, but I personally thought youd have something interesting to convey. All I hear can be a lot of whining about something that you could fix in case you werent too busy interested in attention.

  15. Adbookee says:

    Such a very nice postBus Advertising Agency in Surat

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>