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


Michael Dautermann answer has got everything right, but in order to hide the accessory bar you need to swizzle the method inputAccessoryView() of UIView Class with the inputAccessoryView() of the _NoInputAccessoryView class. I have just added the couple of extra lines to the code which does this job of method swizzling.

First you'll need a fake class to swap with

final class FauxBarHelper: NSObject {
    var inputAccessoryView: AnyObject? { return nil }
}

Then create this method in your controller class

/// Removes the keyboard accessory view from the web view
/// Source: http://stackoverflow.com/a/32620344/308315 / http://stackoverflow.com/a/33939584/308315
func _removeInputAccessoryView(webView: UIWebView) {
    var targetView: UIView? = nil

    for view in webView.scrollView.subviews {
        if String(describing: type(of: view)).hasPrefix("WKContent") {
            targetView = view
        }
    }

    guard let target = targetView else { return }

    let noInputAccessoryViewClassName = "\(target.superclass!)_NoInputAccessoryView"
    var newClass: AnyClass? = NSClassFromString(noInputAccessoryViewClassName)
    if newClass == nil {
        let targetClass: AnyClass = object_getClass(target)
        newClass = objc_allocateClassPair(targetClass, noInputAccessoryViewClassName.cString(using: String.Encoding.ascii)!, 0)
    }

    let originalMethod = class_getInstanceMethod(FauxBarHelper.self, #selector(getter: FauxBarHelper.inputAccessoryView))
    class_addMethod(newClass!.self, #selector(getter: FauxBarHelper.inputAccessoryView), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
    object_setClass(target, newClass)
}

HTH ;)


Here's a slightly safer (no unsafe unwraps) version that works with Swift 4 and (at least) iOS 9 trough 12.

fileprivate final class InputAccessoryHackHelper: NSObject {
    @objc var inputAccessoryView: AnyObject? { return nil }
}

extension WKWebView {
    func hack_removeInputAccessory() {
        guard let target = scrollView.subviews.first(where: {
            String(describing: type(of: $0)).hasPrefix("WKContent")
        }), let superclass = target.superclass else {
            return
        }

        let noInputAccessoryViewClassName = "\(superclass)_NoInputAccessoryView"
        var newClass: AnyClass? = NSClassFromString(noInputAccessoryViewClassName)

        if newClass == nil, let targetClass = object_getClass(target), let classNameCString = noInputAccessoryViewClassName.cString(using: .ascii) {
            newClass = objc_allocateClassPair(targetClass, classNameCString, 0)

            if let newClass = newClass {
                objc_registerClassPair(newClass)
            }
        }

        guard let noInputAccessoryClass = newClass, let originalMethod = class_getInstanceMethod(InputAccessoryHackHelper.self, #selector(getter: InputAccessoryHackHelper.inputAccessoryView)) else {
            return
        }
        class_addMethod(noInputAccessoryClass.self, #selector(getter: InputAccessoryHackHelper.inputAccessoryView), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
        object_setClass(target, noInputAccessoryClass)
    }
}