5

I have a UITableViewController. I want to pop the copy/paste menu up when the user touches a cell. I want to do as in the Contacts app. How to implement this functionality. Can someone help me.

I tried this code,

UIMenuController *theMenu = [UIMenuController sharedMenuController];
[theMenu setTargetRect:CGRectMake(10, 200, 100, 40) inView:[self tableView]];
[theMenu setMenuVisible:YES animated:YES];

But it doesn't work. My question is,

  1. What CGRect I have to pass as the setTargetRect parameter?
  2. Do I need to call SetNeedsDisplayInRect in my TableViewController?
  3. What else to do to make this work?
EmptyStack
  • 51,274
  • 23
  • 147
  • 178

4 Answers4

7

If I am not wrong Copy/Paste menu appears when long pressing a cell in the contact right? If so, I will use UILongPressGestureRecognizer class to get the long press in the cell.

regarding

1: pass the rect of the cell and inView: pass your uitableView

2: I don't think is necessary

3: nothing more:

Something like this should work...

- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"MyCellIdentifier";
    UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
    cell = [[[UITableViewCell alloc] initWithReuseIdentifier:CellIdentifier] autorelease];
        cell.accessoryType = UITableViewCellAccessoryNone;
    cell.indentationWidth = cell.frame.size.height;

    UILongPressGestureRecognizer *r = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(cellWasLongPressed:)];
    [cell addGestureRecognizer:r];
    [r release];
    }

    //configure your cell here
    cell.textLabel.text = [file nameForCell]; 
    return cell;
}

- (void)cellWasLongPressed:(UILongPressGestureRecognizer *)recognizer{

    if (recognizer.state == UIGestureRecognizerStateRecognized) {
        //show your UIMenuHere
        UITableViewCell *cell = (UITableViewCell *)recognizer.view;
        UIMenuController *theMenu = [UIMenuController sharedMenuController];
        [theMenu setTargetRect:cell.frame inView:tableView];
        [theMenu setMenuVisible:YES animated:YES];
    }

}

Note: Above code is brain compiled

Hope it helps ;)

nacho4d
  • 43,720
  • 45
  • 157
  • 240
  • @nacho4d: Thanks for your reply. My class is UITableViewController. SO I set "[theMenu setTargetRect:cell.frame inView:[self view]]". But it doesn't work. I tried [self tableView], again it doesn't work. What view I have to set there? – EmptyStack Jan 03 '11 at 06:13
  • @nacho4d: The UIMenuControllerWillShowMenuNotification is triggering properly. I am not sure its showing or not? But it is not visible. How to make it visible? – EmptyStack Jan 03 '11 at 06:50
  • @nacho4d: I printed the x, y, width and height of menuFrame. Its showing 0 for width. I am setting it properly in the setTargetRect:inView: method. But the width is showing 0 when print it. Any idea why this happens? – EmptyStack Jan 03 '11 at 07:05
  • Ohh!, I c. take a look at this thread: http://stackoverflow.com/questions/3112925/uimenucontroller-not-showing-up you need to do a couple of things more – nacho4d Jan 03 '11 at 08:28
  • Subclass UITableViewCell and override at least -BOOL canBecomeFirstResponder{return YES;} – nacho4d Jan 03 '11 at 08:34
  • @nacho4d: It seems that the problem is somewhere else. I will find it out. Thanks for your help. – EmptyStack Jan 03 '11 at 09:03
  • @nacho4d: Hi.. Thanks man.. Its working great.. Actually I was not properly type casting my UITableViewCell to my CustomCell.. I corrected it now and its works fine.. Thanks for your answer.. Pls add this "stackoverflow.com/questions/3112925/…" in your comment to the answer. – EmptyStack Jan 03 '11 at 11:40
  • @EmptyStack:Hi, having same problem. Still not able to solve.How did you solve this ? – Maulik Jan 21 '13 at 09:02
  • Hi Maulik, What problem do you have? – EmptyStack Jan 22 '13 at 08:55
2

I did some improvements to Ahmet work. Here is resulting classes:

/*
 Copyright 2011 Ahmet Ardal

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

/*
 Improved by Yuriy Umanets <yuriy.umanets@gmail.com>

 Changes:
 - added new property customMenus, which is array of NamedAction instances describing names and selector action
 for each of custom menus (not one of those copy, paste, etc). The class using CopyableCell can provide own actions
 and they will be called by CopyableCell. This is implemented with dynamicMethodIMP() and resolveInstanceMethod();

 - oldStyle field that saves old style of the cell before changing (highlighting) so that when action is taken old
 style is set back to the cell without changing behaviour expected by author;
 - (fix) in touchEnded() one needs to call [super touchEnded] in order to provide correct behaviour.
 */


#import "CopyableCell.h"
#import "NamedAction.h"

static const CFTimeInterval kLongPressMinimumDurationSeconds = 0.5;

@interface CopyableCell(Private)
- (void) initialize;
- (void) menuWillHide:(NSNotification *)notification;
- (void) menuWillShow:(NSNotification *)notification;
- (void) handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer;
@end

@implementation CopyableCell

@synthesize data, indexPath, delegate, customMenus;

- (id) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
        if (!(self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]))
                return self;

        [self initialize];
        return self;
}

- (void) initialize
{
        self.data = nil;
        self.indexPath = nil;
        self.delegate = nil;
        self.customMenus = nil;
        self.selectionStyle = UITableViewCellSelectionStyleNone;

        UILongPressGestureRecognizer *recognizer =
        [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
        [recognizer setMinimumPressDuration:kLongPressMinimumDurationSeconds];
        [self addGestureRecognizer:recognizer];
        [recognizer release];
}

- (void) setSelected:(BOOL)selected animated:(BOOL)animated
{
        [super setSelected:selected animated:animated];
}

- (void) dealloc
{
        [self.customMenus release];
        [self.data release];
        [self.indexPath release];
        [super dealloc];
}

#pragma mark -
#pragma mark Copy Menu related methods

- (BOOL) isCustomActionExists:(SEL)sel
{
        if (self.customMenus != nil) {
                for (int i = 0; i < customMenus.count; i++) {
                        NamedAction *namedItem = [customMenus objectAtIndex:i];
                        if (sel == namedItem.action)
                                return YES;
                }
        }
        return NO;
}

- (BOOL) canPerformAction:(SEL)action withSender:(id)sender
{
        if (self.customMenus != nil) {
                if ([self isCustomActionExists:action])
                        return YES;
        } else {
                if (action == @selector(copy:))
                        return YES;
        }
        return [super canPerformAction:action withSender:sender];
}

- (void) copyToPastboard:(NSString *)dataText
{
        [[UIPasteboard generalPasteboard] setString:dataText];
        [self resignFirstResponder];
}

- (void) copy:(id)sender
{
        if ((self.delegate != nil) &&
            [self.delegate respondsToSelector:@selector(copyableCell:dataForCellAtIndexPath:)]) {
                NSString *dataText = [self.delegate copyableCell:self dataForCellAtIndexPath:self.indexPath];
                [self copyToPastboard:dataText];
        } else if (self.data != nil) {
                [self copyToPastboard:self.data];
        }
}

- (BOOL) canBecomeFirstResponder
{
        return YES;
}

- (BOOL) becomeFirstResponder
{
        return [super becomeFirstResponder];
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
        [super touchesEnded:touches withEvent:event];
        if ([self isFirstResponder] == NO)
                return;

        UIMenuController *menu = [UIMenuController sharedMenuController];
        [menu setMenuVisible:NO animated:YES];
        [menu update];
        [self resignFirstResponder];
}

- (void) menuWillHide:(NSNotification *)notification
{
        if ((self.delegate != nil) && [self.delegate respondsToSelector:@selector(copyableCell:deselectCellAtIndexPath:)])
                [self.delegate copyableCell:self deselectCellAtIndexPath:self.indexPath];

        self.selectionStyle = oldStyle;
        [[NSNotificationCenter defaultCenter] removeObserver:self name:UIMenuControllerWillHideMenuNotification object:nil];
}

- (void) menuWillShow:(NSNotification *)notification
{
        oldStyle = self.selectionStyle;
        self.selectionStyle = UITableViewCellSelectionStyleBlue;
        if ((self.delegate != nil) && [self.delegate respondsToSelector:@selector(copyableCell:selectCellAtIndexPath:)])
                [self.delegate copyableCell:self selectCellAtIndexPath:self.indexPath];

        [[NSNotificationCenter defaultCenter] removeObserver:self name:UIMenuControllerWillShowMenuNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(menuWillHide:)
                                                     name:UIMenuControllerWillHideMenuNotification
                                                   object:nil];
}

#pragma mark -
#pragma mark UILongPressGestureRecognizer Handler Methods

#include <objc/runtime.h>

void dynamicMethodIMP(id self, SEL sel) {
        if ([self isCustomActionExists:sel])
                [((CopyableCell *)self).delegate performSelector:sel withObject:self withObject:nil];
}

+ (BOOL) resolveInstanceMethod:(SEL)sel {
        class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
        return YES;
}

- (void) handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer
{
        if (longPressRecognizer.state != UIGestureRecognizerStateBegan)
                return;

        if ([self becomeFirstResponder] == NO)
                return;

        UIMenuController *menu = [UIMenuController sharedMenuController];
        [menu setTargetRect:self.bounds inView:self];

        NSMutableArray *menuItems = nil;

        if (self.customMenus != nil && customMenus.count > 0) {
                menuItems = [[[NSMutableArray alloc] init] autorelease];
                for (int i = 0; i < customMenus.count; i++) {
                        NamedAction *namedItem = [customMenus objectAtIndex:i];
                        UIMenuItem *emailItem = [[UIMenuItem alloc] initWithTitle:namedItem.name
                                                                           action:namedItem.action];
                        [menuItems addObject:[emailItem autorelease]];
                }
        }
        menu.menuItems = menuItems;

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(menuWillShow:)
                                                     name:UIMenuControllerWillShowMenuNotification
                                                   object:nil];
        [menu setMenuVisible:YES animated:YES];
}

@end

Here is *.h file:

/*
 Copyright 2011 Ahmet Ardal

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */


#import <UIKit/UIKit.h>

@protocol CopyableCellDelegate;

@interface CopyableCell: UITableViewCell
{
        NSString *data;
        NSArray *customMenus;
        NSIndexPath *indexPath;
        UITableViewCellSelectionStyle oldStyle;
}

@property (nonatomic, retain) NSString *data;
@property (nonatomic, retain) NSArray *customMenus;
@property (nonatomic, retain) NSIndexPath *indexPath;
@property (nonatomic, assign) id<CopyableCellDelegate> delegate;

- (void) copyToPastboard:(NSString *)dataText;

@end

@protocol CopyableCellDelegate<NSObject>
@required
- (void) copyableCell:(CopyableCell *)cell selectCellAtIndexPath:(NSIndexPath *)indexPath;
- (void) copyableCell:(CopyableCell *)cell deselectCellAtIndexPath:(NSIndexPath *)indexPath;

@optional
- (NSString *) copyableCell:(CopyableCell *)cell dataForCellAtIndexPath:(NSIndexPath *)indexPath;
@end

Here is helper class header:

#import <Foundation/Foundation.h>

@interface NamedAction : NSObject {
        NSString *name;
        SEL action;
}

@property (nonatomic, retain) NSString *name;
@property (nonatomic, assign) SEL action;

- (id)initWithName:(NSString *)_name action:(SEL)_action;

@end

Implementation:

#import "NamedAction.h"

@implementation NamedAction

@synthesize name, action;

- (id)initWithName:(NSString *)_name action:(SEL)_action
{
        if (self = [super init]) {
                self.name = _name;
                self.action = _action;
        }
        return self;
}

- (void)dealloc {
        self.name = NULL;
        self.action = NULL;
        [super dealloc];
}

@end
Cynichniy Bandera
  • 5,991
  • 2
  • 29
  • 33
2

Subclassing UITableViewCell and implementing copy functionality there is a good idea. I recommend you to have a look at to CopyableCell class. Here is the GitHub link. Hope it helps.

BananaNeil
  • 10,322
  • 7
  • 46
  • 66
Ahmet Ardal
  • 1,162
  • 1
  • 11
  • 12
0

Have a look at this simple way of doing this, it just takes less than 10 lines of code http://notesoncocoa.wordpress.com/2012/03/22/enabling-the-copy-menu-on-a-uitableviewcell/

Harsh Shah
  • 368
  • 3
  • 17