iOS webview,WKWebView长按弹出框UIMenuController添加自定义功能

-先说下背景,`UIMenuController`类基于<UIKit>框架下,是iOS3.0之后发布的,它是一个长按呼出框,自带有复制、剪切、粘贴、全选、删除等等功能。文档里面定义如下图

****本次要实现的功能是 在加载了HTML字符串的UIWebView中实现自定义长按呼出框。

1.默认情况下,UITextFiled、UITextView、UIWebView这三个类及其子类都有自带的长按弹出UIMenuController功能,所有除了这三个以外,有需求的需要自己添加手势实现,实现方法与本文谈到的大同小异。

2.要使当前页面成为第一响应者。

3.屏蔽系统自带的弹出功能


*********下面开始

>首先创建UIMenuController,可以在需要实现的webview的构造方法里面创建代码如下:(此处我根据需求将UIWebView需要加载的内容由初始化赋值,并在调用属性setter方法的时候加载出来)

- (instancetype)initWithFrame:(CGRect)frame content:(NSString *)content
{
    self = [super initWithFrame:frame];
    if (self) {
        
        [self becomeFirstResponder];
        
        [self createMenu];
        
        self.content = content;
    }
    return self;
}


//构建UIMenuController
- (void)createMenu {

    UIMenuController *menu = [UIMenuController sharedMenuController];
    UIMenuItem *item0 = [[UIMenuItem alloc] initWithTitle:@"复制" action:@selector(copy:)];
    UIMenuItem *item1 = [[UIMenuItem alloc] initWithTitle:@"全选" action:@selector(selectAll:)];
    UIMenuItem *item2 = [[UIMenuItem alloc] initWithTitle:@"打印选中文字" action:@selector(logSelectedText:)];
    
    [menu setMenuItems:@[
                         item0,
                         item1,
                         item2]];
}

//内容的setter方法里面加载
- (void)setContent:(NSString *)content {
    
    _content = content;
    [self loadHTMLString:content baseURL:nil];
}


>注意要弹出UIMenuController必须完成三个步骤

 1.在UIWebView中调用becomeFirstResponder方法。

 2.在UIWebView中重写父类方法canBecomeFirstResponder并且 return Yes。

 3.在UIWebView中重写canPerformAction:withSender:方法,来筛选出需要响应的item

 第三点实现代码如下:

//指定menu响应事件屏蔽系统自带响应事件

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {


    if (action ==@selector(copy:) ||

        action == @selector(selectAll:) ||

        action == @selector(logSelectedText:)) {

        

        returnYES;

    }

    

    returnNO;

}

>截止此处,就可以看到弹出的UIMenuController了,效果图如下:


>那么现在还存在俩个问题

 1.赋值和全选都出现了俩次,且他们都是有效的。

 2.打印选中文字还没实现,也没找到API直接获取这个选中文字的。

>针对问题1 是因为系统自带的和我们自定义都筛选出来了,解决办法是可以在我们开始的UIMenuController生成的地方删除这俩个重复的UIMenuItem.

 那么此处涉及到它会显示"copy" "cut"俩个英文。如果支持中文需要到工程中配置一下Project->Info->Localizations里面找到Chinese(simplified)并添加。

 此处注意这样添加后显示的语言是根据手机或者模拟器设置的语言而定的。 配置如下:


>针对问题2  首先需要讲一下 cut: copy:等系统自带的方法,这些方法通通放在了UIResponderStandardEditActions协议中.具体有以下方法:

//    @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
然后就是实现我们自定义的打印出选中文字的方法。我找了一下,并有找到UIMenuController有直接的属性获取选中的文字,固寻求其他解决途径。

       有俩种解决方法仅供参考:

      1.利用js代码直接获取UIWebView中被选中的文字

//UIWebView自带获取选中文字的方法
- (NSString *)getSelectedText {

    return [self stringByEvaluatingJavaScriptFromString:@"window.getSelection().toString()"];
}
2.利用UIMenuController带的赋值方法将内容赋值到粘贴板,再从粘贴板获得所需内容

    //先复制到粘贴板 再打印粘贴板上的内容
    [self copy:menu];
    NSLog(@"选中的文字为:%@", [UIPasteboard generalPasteboard].string);


----------------17.11.6更新-----------------

遇到俩三个同学问我适配WKWebView之后上面的代码不能很好的支持。之前帮一个同学解决了又忘记了,今天又被问到,感谢@灯红酒绿映不出的落寞 提供的思路。

因为WKWeb用相同的方法写会出现一个问题就是不能屏蔽系统的一些方法。

canPerformAction: withSender:
解决办法是把UIMenuController提出来写到ViewController层,所以在这儿需要实现一个方法就是禁止对象取消第一响应

- (BOOL)canBecomeFirstResponder {
    return YES;
}
- (BOOL)canResignFirstResponder {
    return NO;
}
这样写功能是实现了,但是存在一个bug.理论上一个viewcontroller被pop出navigationController的栈之后会释放,但是这个对象似乎没有完全消失,因为我们写了这个对象禁止取消第一响应,其他的对象就不能再响应了。有时候这个bug也会表现为在定义着不同的UIMenuController的不同视图中表现为同一个。所以我们在视图消失的时候应该允许他取消第一响应,而视图存在的时候不行。

- (BOOL)canBecomeFirstResponder {
    return YES;
}
- (BOOL)canResignFirstResponder {
    
    if (_dismissSelf) {
        
        return YES;
    }
    return NO;
}

没有懂的同学把这个判断取消就明白了。我在UIWebView中允许出现“拷贝,全选,打印选中文字”,在WK中允许出现“自定义复制,添加笔记”。

结合Demo,第一次用UIWebView正常,用了WK没加上面判断之后再看UIWebView异常



Github上面已经更新。

----------------18.1.9更新-----------------

有小伙伴问到使用WKWebView用我上面的方法做出来没有全选功能,想要添加一个全选功能。
其实很简单
我试过直接屏蔽函数中添加不屏蔽selectedAll:,但是会造成程序崩溃,有兴趣的可以试试写一个WKWebview的子类屏蔽看看。
canPerformAction: withSender:
另一种思路就是按照之前添加自定义功能的方法添加一个方法叫做“全选”。
1.添加一个UIMenuItem叫做“全选”。
2.在屏蔽函数中打开我们自定义的全选。(注意方法名不要和系统方法名冲突)
3.实现自定义全选。利用[ WKWebView selectAll:]函数,注意实现之后把允许视图取消第一响应的判定改一下,不然会出现全选异能取消的BUG.


遗留了一个bug就是我们使用过一次全选功能之后,以后再打开UIMenuController有几个系统方法就不能屏蔽了(因为之后不会再调用[self  canResignFirstResponder]方法,现在使用Wk的时候系统方法这块确实API有一定的问题,很多实现都是屏蔽UIMenuController采用UIAlertController来替代),希望有解决办法的小伙可以不吝赐教,谢谢!



--------------18.3.29日更新-----------

有小伙伴问到,选中执行菜单项的某个功能后,选中的文本和那对蓝色条都不会消失。我测试了一下确实存在这个问题。这块之前就说过系统API是存在一定问题的,但是迫于功能需求,只能寻找一些不太完美的方法来解决。

1.刚开始想通过执行菜单项的某个功能后,就用代码模拟一次用户点击屏幕来取消掉选中的文字和蓝色条。此处发现一些东西可以和大家分享一下

   >>方法 sendActionsForControlEvents可以对UIControl及其子类直接发送响应,例如UIButton,可以用代码写模拟点击.然而UIView       是UIControl父类,故不能使用

    

  >>方法模拟UITouch和UIEvent 调用 touchesBegan方法,参考文章http://www.cocoawithlove.com/2008/10/synthesizing-touch-event-on-iphone.html 。   其中涉及私有API,可能对上线有影响,感兴趣的可以了解一下。

2.最终还是利用一个取巧的办法,在执行菜单项的某个功能后,重新加载一个web即取消了其选中的文字。

PS:有更好办法的同学欢迎分享交流。


***以上

(欢迎随手给一颗星星哦~)本篇博客Demo地址https://github.com/xmy0010/DemoForCSDN

本人邮箱[email protected]欢迎小伙伴一起讨论,学习,进步。





猜你喜欢

转载自blog.csdn.net/xmy0010/article/details/52945689