UILabel 实现整体拷贝功能

2,253 阅读3分钟
原文链接: www.jianshu.com

iOS 有三个控件自身就可以支持拷贝粘贴等,包括 UITextField,UITextView,UIWebView。而 UILabel 本身是不能响应用户事件也无法复制的,但是可以通过以下设置从而响应系统事件或者是自定义的事件。

首先需要创建一个 UILabel 的子类,并且使其可以成为第一响应者,从而接收用户手势事件。

@interface YasicEditableLabel : UILabel

@end

@implementation YasicEditableLabel

- (BOOL)canBecomeFirstResponder
{
    return YES;
}
@end

接下来要实现 UILabel 的父类 UIResponder 的方法 - (BOOL)canPerformAction:(SEL)action withSender:(nullable id)sender,这个方法表明了当前 View 可以响应哪些方法。而 UIResponder 也定义了一个协议专门表示一些跟编辑有关的方法

@protocol UIResponderStandardEditActions <NSObject>
@optional
- (void)cut:(nullable id)sender NS_AVAILABLE_IOS(3_0);
- (void)copy:(nullable id)sender NS_AVAILABLE_IOS(3_0);
- (void)paste:(nullable id)sender NS_AVAILABLE_IOS(3_0);
- (void)select:(nullable id)sender NS_AVAILABLE_IOS(3_0);
- (void)selectAll:(nullable id)sender NS_AVAILABLE_IOS(3_0);
- (void)delete:(nullable id)sender NS_AVAILABLE_IOS(3_2);
- (void)makeTextWritingDirectionLeftToRight:(nullable id)sender NS_AVAILABLE_IOS(5_0);
- (void)makeTextWritingDirectionRightToLeft:(nullable id)sender NS_AVAILABLE_IOS(5_0);
- (void)toggleBoldface:(nullable id)sender NS_AVAILABLE_IOS(6_0);
- (void)toggleItalics:(nullable id)sender NS_AVAILABLE_IOS(6_0);
- (void)toggleUnderline:(nullable id)sender NS_AVAILABLE_IOS(6_0);

- (void)increaseSize:(nullable id)sender NS_AVAILABLE_IOS(7_0);
- (void)decreaseSize:(nullable id)sender NS_AVAILABLE_IOS(7_0);

@end

其中 copy 就是我们要实现的方法。所以要在自定义的 UILabel 中实现下面两个方法

-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
    return (action == @selector(copy:));
}

-(void)copy:(id)sender
{
    UIPasteboard *pboard = [UIPasteboard generalPasteboard];
    pboard.string = self.text;
}

在第一个方法中返回一个 BOOL 值用于表明对不同的 action 的响应情况,注意如果这里对 copy 返回 YES,而并没有自己实现 copy 方法,后面调用到这个方法的时候就会报 unrecognized selector 错误。

copy 方法里我们用到了 UIPasteboard 对象,也就是管理剪切板的类,它可以通过 generalPasteboard 方法获取到一个系统级别的类,也可以通过下面方法获取到一个有特殊名称的剪切板从而限制可以被获取到的范围,第二个参数用于决定当剪切板不存在时是否创建。

UIPasteboard *pboard = [UIPasteboard pasteboardWithName:@"Yasic" create:YES];

UIPasteboard 还可以获取到一个应用内范围的剪切板。

UIPasteboard *pboard = [UIPasteboard pasteboardWithUniqueName];

同时支持拷贝到剪切板的类有

  • UIPasteboardTypeListString 字符串及数组, 包含 kUTTypeUTF8PlainText
  • UIPasteboardTypeListURL URL及数组,包含 kUTTypeURL
  • UIPasteboardTypeListImage 图形及数组, 包含 kUTTypePNG 和 kUTTypeJPEG
  • UIPasteboardTypeListColor 颜色及数组

这里我们保存到剪切板的字符串中。

接下来还需要确定什么类型的手势能弹起选项弹窗

-(void)attachTapHandler
{
    self.userInteractionEnabled = YES;  
    UITapGestureRecognizer *touch = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
    touch.numberOfTapsRequired = 2;
    [self addGestureRecognizer:touch];
}

-(void)handleTap:(UIGestureRecognizer*) recognizer
{
    [self becomeFirstResponder];
    UIMenuController *menu = [UIMenuController sharedMenuController];
    [menu setTargetRect:self.frame inView:self.superview];
    menu.arrowDirection = UIMenuControllerArrowLeft;
    menu.menuVisible = YES;
}

要注意要弹出弹窗之前首先要成为第一响应者,然后通过 UIMenuController 弹出弹窗,UIMenuController 的 setTargetRect: inView 方法主要用于展示弹窗的位置和弹窗所在 View,arrowDirection 可以指定弹窗方向,最终通过 menuVisible 方法展示弹窗。

这里其实并不需要向 UIMenuController 手动添加选项元素,对于系统支持的方法如 copy、paste 等都是只要在 canPerformAction 中返回了 YES 就会在弹窗选项中自动添加。

那么自然,我们也可以自己实现出现在弹窗的方法,只需要改动下面的方法就可以

-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
    return (action == @selector(myCopy:));
}

- (void)myCopy:(id)sender
{
    UIPasteboard *pboard = [UIPasteboard pasteboardWithUniqueName];
    pboard.string = self.text;
}

-(void)handleTap:(UIGestureRecognizer*) recognizer
{
    [self becomeFirstResponder];
    UIMenuItem *copyLink = [[UIMenuItem alloc] initWithTitle:@"myCopy" action:@selector(myCopy:)];
    [[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObjects:copyLink, nil]];
    [[UIMenuController sharedMenuController] setTargetRect:self.frame inView:self.superview];
    [[UIMenuController sharedMenuController] setMenuVisible:YES animated: YES];
}

这里 UIMenuController 部分动态加入了一个叫做 myCopy 的方法,从而在弹窗中就可以展示这个选项了。