How to implement context menus for cut/copy/paste in a UICollectionView

Screen Shot 2015-02-03 at 11.56.47

By default a collection view cell is implementing a long press gesture recogniser. If activated the cell will bring up the familiar context menu for cut, copy and paste. It’s up to us to enable it, and it’s also up to us how to react to this menu. Here’s how we can do that.

The following three methods are provided as commented stubs in the CollectionViewController template.

Showing the Menu

First we need to tell our class that the menu is to be shown:

- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath {

	return YES;
}

Returning YES here will show the menu for every cell, but since we also get an indexPath, we could make decisions based on which cell has been tapped and make the menu show up conditionally.

Next we need to decide which items we want to show:

- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    
	return YES;
}

Returning YES here will show cut, copy and paste. We can make this conditional too, based both in the indexPath for each cell, but also based on the action parameter. Here’s a variation which will only show cut and paste, but not copy:

- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    
    // no copy option please
    if ([NSStringFromSelector(action) isEqualToString:@"copy:"]) {
        return NO;
    }
    
	return YES;
}

Menu Actions

It’s up to us what happens when either of those menu items is selected by the user. We can either use the UIPasteboard to retrieve and store our values, or create a property and store out entire object in it. I only have an NSString value per cell and will use the UIPasteboard.

This method is called when the user taps a menu item:

- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    
    // Cut
    if ([NSStringFromSelector(action) isEqualToString:@"cut:"]) {
        
        // grab our value
        NSString *value = [self.cellData objectAtIndex:indexPath.row];
        
        // copy object to pasteboard
        [UIPasteboard generalPasteboard].string = value;
        
        // remove item
        [self.cellData removeObjectAtIndex:indexPath.row];
    }
    
    // Copy
    if ([NSStringFromSelector(action) isEqualToString:@"copy:"]) {

        // grab our value
        NSString *value = [self.cellData objectAtIndex:indexPath.row];
        
        // copy object to pasteboard
        [UIPasteboard generalPasteboard].string = value;

    }
    
    // Paste
    if ([NSStringFromSelector(action) isEqualToString:@"paste:"]) {
        
        // grab our value
        NSString *value = [UIPasteboard generalPasteboard].string;
        
        // insert at current position
        [self.cellData insertObject:value atIndex:indexPath.row];
    }
    
    // then reload the collection view
    [self.collectionView reloadData];
	
}

It looks more complex than it really is: we first procure the value of our cell, and then add it to (or remove it from) the underlying data source. Typically it’s a mutable array or Core Data. Once the data is updated, we ask the collection view to reload its data which then makes the changes appear.

That’s it!





Leave a Reply