Category Archives: Tutorials

Mastering Copy and Paste in iOS – Part 2

In part 1 of this series I focused on a very simple locking down of copy and paste in iOS.  Replying NO to canPerformSelector: simply removes the ability to copy and paste from the menu that appears.  Needless to say that can be a little inelegant.  Preventing data leakage can be a good thing but making your app difficult to use is not.  In this post we’ll dig deeper into how you can interact with the copy and paste system to not only prevent leakage but also to work with more than just text.

First let’s get acquainted with some of the components of the copy and paste system.

UIPasteboard

Any time you copy or cut anything in iOS that content gets posted to an instance of UIPasteboard.  The pasteboard is a powerful and type agnostic way to move content from one application to another or between parts of a single application.  The pasteboard is capable of storing nearly any type of data, not just text.

UIMenuController

Screen Shot 2013-05-25 at 3.10.07 PM

UIMenuController is the controller that’s displayed when you highlight text.  Like many other visual controls in iOS this one is pretty locked down.  There are several system items that are added to the menu by default, but you can certainly add your own items via UIMenuItem to do whatever you like.

UIResponderStandardEditActions

This informal protocol is conformed to by descendants of UIResponder to handle the common actions provided by UIMenuController.  UIMenuController will start looking for objects in the responder chain with the first responder moving up the hierarchy until it finds a valid target for the various messages offered by the specified action, such as copy: or cut:.  As we saw in part 1, objects responding to canPerformAction: alter the options provided by the UIMenuController.

The way these three interact isn’t entirely obvious, so we’ll build a sample application that shows placing data in an application specific pasteboard, uses a custom image view that conforms to the UIResponderStandardEditActions protocol supporting cutting and pasting, and adding some custom functionality to the UIMenuController that will appear over our control.  You can download the sample project here.

Setting up the project

We’ll start with a master-detail template.  The master view controller will contain a list of table cells with an image and some text.  The detail view will show the image displayed.  This isn’t a post about table views and interface builder, so I recommend just checking out the sample code if you’re not comfortable with the basic setup.

The custom control

Our custom control will simply be a UIImageView that implements the basic methods for cut, copy, and paste.  To get the implementation correct it’s important to think about what a user’s expectation would be for these three actions:

  • copy – the image is made available to paste into other controls that can accept an image as content.
  • cut – the image is made available to paste into other controls that can accept the image and the image is removed from the current control.
  • paste – the displayed image is replaced with whatever image is in the clipboard.

Implementing these three methods from UIResponderStandardEditActions is actually quite simple:

- (void)copy:(id)sender
{
    UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
    [pasteboard setImage:self.image];
}

- (void)paste:(id)sender
{
    UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
    if (pasteboard.image) self.image = pasteboard.image;
}

- (void)thumb:(id)sender
{
    UIImage *thumb = [self thumbnailFromImage:self.image];
    [[UIPasteboard generalPasteboard] setImage:thumb];
}

Easy, right? The copy method will add the UIImageView’s image to the pasteboard via setImage. Conversely, paste will place the image from the pasteboard if it exists. Cut will first call copy to place the image to the pasteboard then clear out the image property.

Adding the UIMenuController

You can run this code now, however there’s no way to actually access the cut, copy, and paste methods.  We’ll have to do a couple of things to get the UIMenuController to appear and allow us to copy and paste the image content.

  1. Add a long press gesture recognizer to our control.
  2. On the action for gesture recognizer we’ll make our control the first responder.
  3. Lastly show the menu controller.

Adding the gesture recognizer is easy.  See the code below for creating the UIMenuController:

- (void)handleLongPress:(UILongPressGestureRecognizer *)sender
{
    NSLog(@"long press");

    UIMenuController *menu = [UIMenuController sharedMenuController];
    if (![menu isMenuVisible])
    {
        [self becomeFirstResponder];
        [menu setTargetRect:self.frame inView:self.superview];
        [menu setMenuVisible:YES animated:YES];
    }
}

You’ll notice that the menu controller is a singleton, and this is the only way to instantiate one. If the menu isn’t shown already we’ll make our image view the first responder and display the menu. The critically important step is to register ourselves as the first responder. Remember that the UIMenuController looks for the protocol methods using the responder chain. No other class in our view will respond to these methods. Without setting ourselves as the first responder the UIMenuViewController won’t display at all.

Since we implement cut, copy, and paste, those are the three buttons presented in the menu controller.  At this point you can run the app and successfully copy and paste between the image views.  You can also try jumping out to Mobile Safari, long pressing an image, selecting copy and pasting it into one of your image views if you’re still skeptical.

Adding custom actions to the menu

UIMenuController exposes the menuItems property allowing you to add custom menu items.  The menu items are added after any system items and must be instances of UIMenuItem.  We’ll add a custom menu item to copy a thumbnail of the image to the pasteboard.

Creating the thumbnail is somewhat out of scope for a detailed explanation, but here’s the code below.  It takes an image and returns one-third the size of the original (source material taken from here):

- (UIImage *)thumbnailFromImage:(UIImage *)source
{
    CGSize originalSize = source.size;
    CGSize destSize = CGSizeMake(originalSize.width / 3, originalSize.height / 3);

    UIGraphicsBeginImageContext(destSize);
    [ source drawInRect:(CGRect){CGPointZero, destSize}];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return newImage;
}

Next we need to create a method on our image view to place the thumbnail on the pasteboard:

- (void)thumb:(id)sender
{
    UIImage *thumb = [self thumbnailFromImage:self.image];
    [[UIPasteboard generalPasteboard] setImage:thumb];
}

Lastly we need to create the UIMenuItem to add to the UIMenuController. The UIMenuItem class is very simple and only takes a title and selector. Since we added the method thumb: to our image view that’s the selector we’ll reference in the menu item.

UIMenuItem *item = [[UIMenuItem alloc] initWithTitle:@"Copy Thumbnail" action:@selector(thumb:)];
menu.menuItems = @[item];

We’ll place this code right before showing the menu controller in the long press handler. You can now run the app, select “Copy Thumbnail” and you’ll see the image pasted is a thumbnail relative to the original.

Note: If you created the nib for the detail view yourself, it’s important the view mode to “center” to avoid the image view stretching the thumbnail when you paste it.

Creating an application specific pasteboard

Up until this point we’ve been using the standard general pasteboard but the SDK provides us other options as well. The only requirement for creating a new one is that the name must be unique. You might also mark a pasteboard as persistent so it will live on after your app terminates. This isn’t particularly useful for a single application but can be extremely powerful for preventing data leakage while still maintaining copy and paste functionality between a suite of apps.

We’ll define the custom pasteboard name in the application delegate’s header file. We can import that into any other file where I need the pasteboard name.

// App delegate header
extern NSString *const pasteboardIdentifier;

I’ll also initialize the pasteboard in applicationDidLoad method on the app delegate:

// app delegate implementation file
// make sure to define the extern
NSString *const pasteboardIdentifier = @"com.pzearfoss.customCopyPaste";

// ...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // ...
    [UIPasteboard pasteboardWithName:pasteboardIdentifier create:YES];
    // ...
}

Lastly, in my image view I’ll replace the generalPasteboard call with pasteboardWithName:create:

- (void)copy:(id)sender
{
    UIPasteboard *pasteboard = [UIPasteboard pasteboardWithName:pasteboardIdentifier create:NO];
    [pasteboard setImage:self.image];
}

- (void)paste:(id)sender
{
    UIPasteboard *pasteboard = [UIPasteboard pasteboardWithName:pasteboardIdentifier create:NO];
    if (pasteboard.image) self.image = pasteboard.image;
}

- (void)thumb:(id)sender
{
    UIImage *thumb = [self thumbnailFromImage:self.image];
    [[UIPasteboard pasteboardWithName:pasteboardIdentifier create:NO] setImage:thumb];
}

Running the application you’ll now see that you can copy and paste between image views as you could before, but if you try to copy from another app like Mobile Safari it will not paste in our app.

That concludes part 2 for implementing copy and paste, UIMenuController, and using an app specific UIPasteboard. Check out part 3 where we’ll store more complex data types in the pasteboard.

Advertisements

Mastering Copy and Paste in iOS – Part 1

System wide copy and paste is built into most of the stock iOS controls without the developer having to do any extra work to enable the feature.  Sometimes for security purposes you may wish to lock down the functionality (to not allow copy and paste at all) or disallow copying and pasting between your app and other apps that may be on the device.  Restricting this functionality is a pretty common requirement for the growing market of security frameworks out there for enterprise applications such as GOOD and MobileIron.  They commonly refer to this as “data leakage”.

In this 3-part series I’ll explore the copy and paste system in iOS and how you can use it to fit the needs of your specific application.

This first part will focus on locking down the copy and paste system completely.

In the second part I’ll show you how you can create an application specific pasteboard to allow copy and paste within your own application while still preventing data leakage to other apps as well as posting other types of data to UIPasteboard.

In the third part I’ll explore other types of data on the pasteboard with simple drawing sample app.

Locking down copy and paste

All controls in UIKit expose a single method you can override to control actions such as copy and paste..  To lock down system wide we really only need to implement a category on the specific controls.  In preparing the sample code for this I attempted a category on UIResponder, but that seems to have unintended side effects. Adding it to the classes directly seemed to be the most consistent approach.

At a high level you’ll want to use code like this:

@interface UITextField (lockdown) @end
@implementation UITextField (lockdown)
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
    SEL copy = @selector(copy:);
    SEL cut = @selector(cut:);
    SEL paste = @selector(paste:);
    if (action == copy ||
        action == cut ||
        action == paste)
    {
        return NO;
    }
    return YES;
}
@end

You can find the sample code for this here that will lock down UITextField, UITextView, and UISearchBar.  Remember that since this method exists on UIResponder, you can use it to lock down any control, even complex ones like UIWebView.

One more note:

When you first implement this you may notice that you get a compiler warning about reimplementing a method in a category that already has an implementation elsewhere.

You can prevent this code by wrapping it in the following:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
// ...
#pragma clang diagnostic pop

Be sure to check out part 2 of this series for digging deeper into copy and paste and the UIResponderStandardEditActions protocol.

Some great iOS links from the past week.

Here’s a roundup of some of the things I’ve been reading from around the web this week:

Building mobile applications course at Harvard Extension School. I’ve been meaning to learn some android development. I think this is where I’m going to start.

NounProject – Need some icons for your next app? Give these a look!

Catching integer overflows in C – It certainly doesn’t come up very often, but every once in a while you’ll have to deal with integer overflows. This article details what they are and how to catch them.

Nextive JSON parser – I haven’t had opportunity to try this out yet, but word on the street is this is the fastest JSON lib around.

When Patents Attack – produced by NPR and This American Life, this expose talks about patent trolls like lodsys and their shady business practices. Here’s the direct link to the This American Life Podcast.

New iOS Devs Shouldn’t Use IB – Like this author, I’m an IB convert but when I started I was doing all my interfaces in code. The author talks about why all iOS devs should start like this. What do you think?

25 Amazing Open Source iPhone Apps – There’s no better way to learn than seeing how others do it. Check out these open source iPhone products.

Jeff LaMarche on the runtime and properties

Some time ago I posted about printing all the properties of an object using the runtime.  I was poking around the NSConference site today and found a talk by Jeff LaMarche on the same topic talking about ways to use the runtime in your application design patterns:

Cocoa Design Patterns that Leverage the Objective-C Runtime (NSConference 2010) from iDeveloper TV on Vimeo.