WKWebView
があり、編集メニューからシステムメニュー項目(コピー、定義、共有...)を削除して、自分の項目を表示したいと思います。
私はiOS8と9をターゲットにしています。現在、Xcode 7.0.1シミュレーター(iOS 9)とiOS9.0.2を実行しているiPhone6でテストしています。
これを実現する標準的な方法は、WKWebView
をサブクラス化し、_-canPerformAction:withSender:
_を実装することです。ただし、WKWebView
_-canPerformAction:withSender:
_では_copy:
_または_define:
_アクションが呼び出されていないことがわかりました。これは既知のバグのようです( WKWebViewおよびUIMenuController )。
アプリの例: https://github.com/dwieringa/WKWebViewCustomEditMenuBug
_@implementation MyWKWebView
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
NSLog(@"ACTION: %@", NSStringFromSelector(action));
if (action == @selector(delete:))
{
// adding Delete as test (works)
return YES;
}
// trying to remove everything else (does NOT work for Copy, Define, Share...)
return NO;
}
- (void)delete:(id)sender
{
NSLog(@"Delete menu item selected");
}
@end
_
出力:(_copy:
_または_define:
_アクションがないことに注意してください)
_2015-10-20 12:28:32.864 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: cut:
2015-10-20 12:28:32.865 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: select:
2015-10-20 12:28:32.865 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: selectAll:
2015-10-20 12:28:32.865 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: paste:
2015-10-20 12:28:32.866 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: delete:
2015-10-20 12:28:32.866 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: _promptForReplace:
2015-10-20 12:28:32.866 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: _transliterateChinese:
2015-10-20 12:28:32.867 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: _showTextStyleOptions:
2015-10-20 12:28:32.907 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: _addShortcut:
2015-10-20 12:28:32.908 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: _accessibilitySpeak:
2015-10-20 12:28:32.908 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: _accessibilitySpeakLanguageSelection:
2015-10-20 12:28:32.908 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: _accessibilityPauseSpeaking:
2015-10-20 12:28:32.909 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: makeTextWritingDirectionRightToLeft:
2015-10-20 12:28:32.909 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: makeTextWritingDirectionLeftToRight:
_
私の望みは、編集メニューを完全に非表示にして、 QBPopupMen を使用してカスタムメニューに置き換えることです。
私の問題は、標準の[編集]メニューを非表示または無効にする方法を見つけることができなかったことです。 UIMenuControllerWillShowMenuNotification
の_[UIMenuController sharedMenuController].menuVisible = NO;
_で非表示にする提案をいくつか見つけましたが、これを機能させることができませんでした。 WillShowMenu
には影響しません。 DidShowMenu
で非表示にすることはできますが、その時点では手遅れで、メニューが点滅します。
また、[[UIMenuController sharedMenuController] setTargetRect:CGRectMake(0, 0, 1, 1) inView:self.extraView];
を使用して表示領域の外側に配置しようとしましたが、WillShowMenu
を使用しても効果がなく、DidShowMenu
を使用すると手遅れになります。
ここで利用可能な実験: https://github.com/dwieringa/WKWebViewEditMenuHidingTest
何が足りないのですか? WKWebViewの標準編集メニューを無効または非表示にする別の方法はありますか?
あなたの回避策に基づいて、私はそれを見つけました:
-(void)menuWillShow:(NSNotification *)notification
{
NSLog(@"MENU WILL SHOW");
dispatch_async(dispatch_get_main_queue(), ^{
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:NO];
});
}
メニューが90%の回数点滅するのを防ぎます。それでも十分ではありませんが、適切な解決策を見つける前の別の回避策です。
ビューコントローラをファーストレスポンダーにして、ファーストレスポンダーを辞任しないようにしてください
- (BOOL)canResignFirstResponder {
return NO;
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
https://github.com/dwieringa/WKWebViewEditMenuHidingTest/pull/1
Stephan Heilnerのソリューションを試しましたが、Swift 4でコンパイルされませんでした。
これは、Swift 4で動作するWKWebViewでmenuControllerを無効にするための私の実装です。
私のWKWebViewサブクラスでは、次のプロパティと関数を追加しました。
_var wkContentView: UIView? {
return self.subviewWithClassName("WKContentView")
}
private func swizzleResponderChainAction() {
wkContentView?.swizzlePerformAction()
}
_
次に、同じファイルに拡張子を追加しましたが、WKWebViewサブクラスから追加しました。
_// MARK: - Extension used for the swizzling part linked to wkContentView (see above)
extension UIView {
/// Find a subview corresponding to the className parameter, recursively.
func subviewWithClassName(_ className: String) -> UIView? {
if NSStringFromClass(type(of: self)) == className {
return self
} else {
for subview in subviews {
return subview.subviewWithClassName(className)
}
}
return nil
}
func swizzlePerformAction() {
swizzleMethod(#selector(canPerformAction), withSelector: #selector(swizzledCanPerformAction))
}
private func swizzleMethod(_ currentSelector: Selector, withSelector newSelector: Selector) {
if let currentMethod = self.instanceMethod(for: currentSelector),
let newMethod = self.instanceMethod(for:newSelector) {
let newImplementation = method_getImplementation(newMethod)
method_setImplementation(currentMethod, newImplementation)
} else {
print("Could not find originalSelector")
}
}
private func instanceMethod(for selector: Selector) -> Method? {
let classType = type(of: self)
return class_getInstanceMethod(classType, selector)
}
@objc private func swizzledCanPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
_
そして最後に、初期化子からswizzleResponderChainAction()
関数を呼び出しました(指定された初期化子をオーバーライドするか、便利な初期化子を作成できます)。
_override init(frame: CGRect, configuration: WKWebViewConfiguration) {
super.init(frame: frame, configuration: configuration)
swizzleResponderChainAction()
}
_
これで、UIMenuControllerを使用するときにWKWebViewがクラッシュしなくなりました。
何時間も費やした後、100%の成功率で汚い解決策を見つけました。
ロジックは; UIMenuControllerがいつ表示されたかを検出し、更新します。
ViewController(WKWebViewを含む)で、次のようにviewDidLoad()にUIMenuControllerDidShowMenuオブザーバーを追加します。
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(uiMenuViewControllerDidShowMenu),
name: NSNotification.Name.UIMenuControllerDidShowMenu,
object: nil)
}
Deinitでオブザーバーを削除することを忘れないでください。
deinit {
NotificationCenter.default.removeObserver(
self,
name: NSNotification.Name.UIMenuControllerDidShowMenu,
object: nil)
}
そして、セレクターで、UIMenuControllerを次のように更新します。
func uiMenuViewControllerDidShowMenu() {
if longPress {
let menuController = UIMenuController.shared
menuController.setMenuVisible(false, animated: false)
menuController.update() //You can only call this and it will still work as expected but i also call setMenuVisible just to make sure.
}
}
UIMenuControllerを呼び出すViewControllerでは、このメソッドが呼び出されます。私はブラウザアプリを開発しているので、searchBarもあり、ユーザーはそこにテキストを貼り付けたいと思うかもしれません。そのため、WebビューでlongPressを検出し、UIMenuControllerがWKWebViewによって呼び出されるかどうかを確認します。
このソリューションはgifのように動作します。メニューは一瞬表示されますが、タップすることはできません。フェードアウトする前にタップしてみることができますが、成功しません。結果を教えてください。
私はそれが誰かを助けることを願っています。
乾杯。
このバグは、実際には、プライベートクラスであるWKContentViewに追加されているアクションが原因で発生します。 UIView拡張機能を追加して、次のように回避できます。
import UIKit
extension UIView {
open override class func initialize() {
guard NSStringFromClass(self) == "WKContentView" else { return }
swizzleMethod(#selector(canPerformAction), withSelector: #selector(swizzledCanPerformAction))
}
fileprivate class func swizzleMethod(_ selector: Selector, withSelector: Selector) {
let originalSelector = class_getInstanceMethod(self, selector)
let swizzledSelector = class_getInstanceMethod(self, withSelector)
method_exchangeImplementations(originalSelector, swizzledSelector)
}
@objc fileprivate func swizzledCanPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
少し観察して修正しました。
-canPerformAction:withSender:
で、_share
および_define
オプションに対してNO
を返します。これは、プロジェクトでそれらを必要としないためです。初めてWordを選択すると期待どおりに機能しますが、2回目からオプションが表示されます。
簡単な修正:tapGuesture
に[self becomeFirstResponder];
を追加するか、デリゲートメソッドをタッチします
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender {
SEL defineSEL = NSSelectorFromString(@"_define:");
if(action == defineSEL){
return NO;
}
SEL shareSEL = NSSelectorFromString(@"_share:");
if(action == shareSEL){
return NO;
}
return YES;
}
// Tap gesture delegate method
- (void)singleTap:(UITapGestureRecognizer *)sender {
lastTouchPoint = [sender locationInView:self.webView];
[self becomeFirstResponder]; //added this line to fix the issue//
}
これが私の最終的な解決策であり、ここに投稿された解決策を応用したものです。重要なのは、UIMenuControllerWillShowMenu
通知をリッスンしてから、Dispatch.main.async
メニューを非表示にします。これは、メニューの点滅を回避するためのトリックを行うようです。
私の例ではUITextField
を使用していますが、WKWebView
に簡単に適合させる必要があります。
class NoMenuTextField: UITextField {
override func didMoveToSuperview() {
super.didMoveToSuperview()
if superview == nil {
deregisterForMenuNotifications()
} else {
registerForMenuNotifications()
}
}
func registerForMenuNotifications() {
NotificationCenter.default.addObserver(forName: Notification.Name.UIMenuControllerWillShowMenu,
object: nil,
queue: OperationQueue.main)
{ _ in
DispatchQueue.main.async {
UIMenuController.shared.setMenuVisible(false, animated: false)
UIMenuController.shared.update()
}
}
}
func deregisterForMenuNotifications() {
NotificationCenter.default.removeObserver(self,
name: Notification.Name.UIMenuControllerWillShowMenu,
object: nil)
}
}
IOS 11では、WKWebViewの拡張による簡単な解決策を見つけました。これが以前のバージョンのiOSで機能するかどうかは確認していません。以下は、1つのメニュー項目を持つ簡単な例です。
import UIKit
import WebKit
extension WKWebView {
override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
switch action {
case #selector(highlightHandler):
return true
default:
return false
}
}
func createEditMenu() { // Should be called once
let highlight = UIMenuItem(title: "Highlight", action: #selector(highlightHandler))
menuItems.append(highlight)
UIMenuController.shared.menuItems = [highlight]
}
@objc func highlightHandler(sender: UIMenuItem) {
print("highlight clicked")
}
}
私が使用した1つの方法は、CSSを使用してメニューを無効にすることです。 CSSプロパティは -webkit-touch-callout: none;
。これを最上位の要素に適用して、ページ全体または任意の子要素に対して無効にし、より正確に無効にすることができます。お役に立てば幸いです。
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
// Add:
// Disable LongPress and Selection, no more UIMenucontroller
[self.wkWebView evaluateJavaScript:@"document.documentElement.style.webkitUserSelect='none'" completionHandler:nil];
[self.wkWebView evaluateJavaScript:@"document.documentElement.style.webkitTouchCallout='none'" completionHandler:nil]; }