Removing Wkwebview Accesory Bar in Swift

Removing WKWebView Accesory bar in Swift

For those who are still looking, the WebKit team updated WKWebView (iOS 13+) so that you can subclass it to remove/update the input accessory view:

https://trac.webkit.org/changeset/246229/webkit#file1

In Swift, I subclassed it, and returned nil. Worked as expected. I hope it helps.

FYI: I checked the docs, and it doesn't mention not to subclass WKWebView, so subclassing is allowed.

import WebKit

class RichEditorWebView: WKWebView {

var accessoryView: UIView?

override var inputAccessoryView: UIView? {
// remove/replace the default accessory view
return accessoryView
}

}

You can find a working version of it here: https://github.com/cbess/RichEditorView/commits/master

Hiding Keyboard accessorybar in WKWebView

This is possible in WKWebView with a variant of the method for swizzling out the inputAccessoryView on UIWebView.

First, add this little class:

@interface _NoInputAccessoryView : NSObject

@end

@implementation _NoInputAccessoryView

- (id)inputAccessoryView {
return nil;
}

@end

Next, add this method:

- (void)removeInputAccessoryViewFromWKWebView:(WKWebView *)webView {
UIView *targetView;

for (UIView *view in webView.scrollView.subviews) {
if([[view.class description] hasPrefix:@"WKContent"]) {
targetView = view;
}
}

if (!targetView) {
return;
}

NSString *noInputAccessoryViewClassName = [NSString stringWithFormat:@"%@_NoInputAccessoryView", targetView.class.superclass];
Class newClass = NSClassFromString(noInputAccessoryViewClassName);

if(newClass == nil) {
newClass = objc_allocateClassPair(targetView.class, [noInputAccessoryViewClassName cStringUsingEncoding:NSASCIIStringEncoding], 0);
if(!newClass) {
return;
}

Method method = class_getInstanceMethod([_NoInputAccessoryView class], @selector(inputAccessoryView));

class_addMethod(newClass, @selector(inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method));

objc_registerClassPair(newClass);
}

object_setClass(targetView, newClass);
}

Then all you have to do is call that method and pass in your WKWebView:

[self removeInputAccessoryViewFromWKWebView:webView];

Note: I do not yet know for sure if this will pass app review, but it is extremely similar to the same code I used for UIWebView, and that did pass review.

Update: This code is in an app that has passed App Store review.

Modifying keyboard toolbar / accessory view with WKWebView

Maybe this tutorial on UITextInputAssistantItem would be helpful.

That said, after fiddling around with this for a while, using WKWebView I still could only get this to work for the first time the keyboard displayed, and every time after that it would return to its original state. The only thing I found that consistently worked was something like the following:

class ViewController: UIViewController {

@IBOutlet weak private var webView: WKWebView!
private var contentView: UIView?

override func viewDidLoad() {
super.viewDidLoad()
webView.loadHTMLString("<html><body><div contenteditable='true'></div></body></html>", baseURL: nil)
for subview in webView.scrollView.subviews {
if subview.classForCoder.description() == "WKContentView" {
contentView = subview
}
}
inputAssistantItem.leadingBarButtonGroups = [UIBarButtonItemGroup(barButtonItems: [UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed(_:)))], representativeItem: nil)]
inputAssistantItem.trailingBarButtonGroups = [UIBarButtonItemGroup(barButtonItems: [UIBarButtonItem(barButtonSystemItem: .camera, target: self, action: #selector(openCamera(_:))), UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(actionPressed(_:)))], representativeItem: nil)]
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow), name: .UIKeyboardDidShow, object: nil)
}

@objc private func donePressed(_ sender: UIBarButtonItem) {
view.endEditing(true)
}

@objc private func openCamera(_ sender: UIBarButtonItem) {
print("camera pressed")
}

@objc private func actionPressed(_ sender: UIBarButtonItem) {
print("action pressed")
}

@objc private func keyboardDidShow() {
contentView?.inputAssistantItem.leadingBarButtonGroups = [UIBarButtonItemGroup(barButtonItems: [UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed(_:)))], representativeItem: nil)]
contentView?.inputAssistantItem.trailingBarButtonGroups = [UIBarButtonItemGroup(barButtonItems: [UIBarButtonItem(barButtonSystemItem: .camera, target: self, action: #selector(openCamera(_:))), UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(actionPressed(_:)))], representativeItem: nil)]
}

}

It will give something like this:

Sample Image

It's a bit frustrating to have to set this on the content view every time the keyboard shows, as well as on the view controller itself, and I hope there's a better way to do this.... But unfortunately I could not find it.

How to edit accessory view of keyboard shown from WKWebView?

Found a way, ended up to swizzle UIToolbars. Hopefully everything is there, but you would get an idea. Swift 4:

class YourController: UIViewController {

@IBOutlet weak var webView: PWebView!
var toolbar : UIToolbar?

func viewDidLoad() {
webView.addInputAccessoryView(toolbar: self.getToolbar(height: 44))
}

func getToolbar(height: Int) -> UIToolbar? {
let toolBar = UIToolbar()
toolBar.frame = CGRect(x: 0, y: 50, width: 320, height: height)
toolBar.barStyle = .black
toolBar.tintColor = .white
toolBar.barTintColor = UIColor.blue

let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(onToolbarDoneClick(sender:)) )
let flexibleSpaceItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil )

toolBar.setItems([flexibleSpaceItem, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true

toolBar.sizeToFit()
return toolBar
}

@objc func onToolbarDoneClick(sender: UIBarButtonItem) {
webView?.resignFirstResponder()
}
}

var ToolbarHandle: UInt8 = 0

extension WKWebView {

func addInputAccessoryView(toolbar: UIView?) {
guard let toolbar = toolbar else {return}
objc_setAssociatedObject(self, &ToolbarHandle, toolbar, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)

var candidateView: UIView? = nil
for view in self.scrollView.subviews {
let description : String = String(describing: type(of: view))
if description.hasPrefix("WKContent") {
candidateView = view
break
}
}
guard let targetView = candidateView else {return}
let newClass: AnyClass? = classWithCustomAccessoryView(targetView: targetView)

guard let targetNewClass = newClass else {return}

object_setClass(targetView, targetNewClass)
}

func classWithCustomAccessoryView(targetView: UIView) -> AnyClass? {
guard let _ = targetView.superclass else {return nil}
let customInputAccesoryViewClassName = "_CustomInputAccessoryView"

var newClass: AnyClass? = NSClassFromString(customInputAccesoryViewClassName)
if newClass == nil {
newClass = objc_allocateClassPair(object_getClass(targetView), customInputAccesoryViewClassName, 0)
} else {
return newClass
}

let newMethod = class_getInstanceMethod(WKWebView.self, #selector(WKWebView.getCustomInputAccessoryView))
class_addMethod(newClass.self, #selector(getter: WKWebView.inputAccessoryView), method_getImplementation(newMethod!), method_getTypeEncoding(newMethod!))

objc_registerClassPair(newClass!)

return newClass
}

@objc func getCustomInputAccessoryView() -> UIView? {
var superWebView: UIView? = self
while (superWebView != nil) && !(superWebView is WKWebView) {
superWebView = superWebView?.superview
}

guard let webView = superWebView else {return nil}

let customInputAccessory = objc_getAssociatedObject(webView, &ToolbarHandle)
return customInputAccessory as? UIView
}
}

Hide shortcut keyboard bar for UIWebView in iOS 9

Using method swiziling we can remove the keyboard shortcut bar (only works with ObjC).

 - (void)hideKeyboardShortcutBar
{
Class webBrowserClass = NSClassFromString(@"UIWebBrowserView");
Method method = class_getInstanceMethod(webBrowserClass, @selector(inputAccessoryView));

IMP newImp = imp_implementationWithBlock(^(id _s) {
if ([self.webView respondsToSelector:@selector(inputAssistantItem)]) {
UITextInputAssistantItem *inputAssistantItem = [self.webView inputAssistantItem];
inputAssistantItem.leadingBarButtonGroups = @[];
inputAssistantItem.trailingBarButtonGroups = @[];
}
return nil;
});

method_setImplementation(method, newImp);
}

inputAccessoryView
: This property is typically used to attach an accessory view to the
system-supplied keyboard that is presented for UITextField and
UITextView objects.

So the new implementation block will be fired every time the keyboard pops up.

UPDATE

To remove the accessory view from WKWebView use WKContentView instead of UIWebBrowserView



Related Topics



Leave a reply



Submit