10

I am trying for a few days now to get this converted into Swift without really having much background with it.

This is what I've got so far ... and I have been looking on google not really knowing what to search for in order to be more specific. Can you please shed some light on what I'm doing wrong ? Thanks

Update:

I have aded the objective-c tag just so more people that are related to this thread may be able to see it and hopefully get an answer.

enter image description here

Community
  • 1
  • 1
Eduard
  • 3,395
  • 8
  • 37
  • 62

4 Answers4

18

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

C. Bess
  • 567
  • 7
  • 10
  • 3
    Thank you so much for doing the Lord's working porting RichEditorView to WKWebView... saved me a ton of time!!! – zaitsman Sep 30 '19 at 05:33
  • Brilliant. My app was crashing any time I tapped on a text input in my webview. It turned out that the problem had to do with trying to display the accessory bar. In this case, the accessory bar would look like an up arrow, a down arrow, and the word 'Done', trying to help a user navigate a form. This solution stopped my app from crashing. – Steven Love Feb 12 '21 at 05:27
9

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 ;)

iwasrobbed
  • 46,496
  • 21
  • 150
  • 195
iamyogish
  • 2,372
  • 2
  • 23
  • 40
  • 1
    @socca1157 Thanks for suggestion, I'd try to write one on Medium, I will keep you posted :) – iamyogish Apr 25 '16 at 04:04
  • 1
    @iWasRobbed thanks for updating the answer to Swift 3. – iamyogish Nov 17 '16 at 04:40
  • Any update on this? Can't seem to get it to work running the latest version of Swift and iOS. – JDev May 22 '17 at 23:02
  • 5
    Small note: I hit an issue with this approach when using dictation. Got a weird crash from `UIDictationController`. The `newClass` isn't visible to Obj-C, adding `objc_registerClassPair(newClass)` right after `objc_allocateClassPair(…)` call seems to fix the issue. – eeeee May 23 '17 at 21:59
  • Like @eeeee I observed a crash when using the above code with voice dictation. I recommend making his suggested changes. – Jacob Feb 22 '18 at 18:03
  • @eeeee Thanks so much for that comment, I ran into the same issue and it took me a day to find out that it was related to the accessory view, then I was stuck. Your fix works perfectly! – kevin Jun 29 '18 at 07:56
8

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)
    }
}
Oscar Apeland
  • 6,422
  • 7
  • 44
  • 92
3

This code snippet should get you over your issue:

class _NoInputAccessoryView: NSObject {

    func removeInputAccessoryViewFromWKWebView(webView: WKWebView) {
        // make sure to make UIView an optional here...
        var targetView: UIView? = nil
        for view in webView.scrollView.subviews {
            if String(view.dynamicType).hasPrefix("WKContent") {
                targetView = view
            }
        }

        // only optionals can be nil
        if targetView == nil {
            return
        }

        let noInputAccessoryViewClassName = "\(targetView!.superclass)_NoInputAccessoryView"
        var newClass : AnyObject? = NSClassFromString(noInputAccessoryViewClassName)
        if newClass == nil {
            let uiViewClass : AnyClass = object_getClass(targetView!)
            newClass = objc_allocateClassPair(uiViewClass, noInputAccessoryViewClassName.cStringUsingEncoding(NSASCIIStringEncoding)!, 0)
        }
    } 

You can also use "String(view.dynamicType)" to get the class name of the object you're looking at, as I noticed via this answer as I was researching the way to solve your problem.

Using hasPrefix like that in both Objective-C and Swift is really hacky and perhaps a better way of hiding the keyboard could be found for production code?

Community
  • 1
  • 1
Michael Dautermann
  • 88,797
  • 17
  • 166
  • 215
  • I have done the changes and a small fix which I edited your post with but now i get [this](http://i.imgur.com/1q9UzPi.png). I've tried adding `newClass: AnyClass? = NSClassFromString` which solves the warning and the `newClass == nil` comparison but then it leaves me with the third error, `Cannot convert value of type UIView to expected type AnyClass!` – Eduard Nov 22 '15 at 11:38
  • I've made an adjustment to my code... give it a try now. – Michael Dautermann Nov 22 '15 at 12:21
  • one more stupid question, how do I apply this class on top of the webview object ? I'm just new and stupid.. sorry – Eduard Nov 22 '15 at 13:08
  • 1
    it's not a stupid question and neither are you. If you look at the original Objective-C source code you're porting, the author made his "`removeInputAccessoryViewFromWKWebView`" function a part of the view controller. You're doing yours as a standalone class deriving from NSObject. Assuming you're calling this from a pure Swift view controller, you'd need to do something like "`let navHelper = _NoInputAccessoryView.init()`" and "`navHelper. removeInputAccessoryViewFromWKWebView(passInWebViewHere)`". Makes sense? – Michael Dautermann Nov 22 '15 at 13:11
  • Also, you really should change the name of that class from "`_NoInputAccessoryView`" to something that doesn't start with an underscore (which is a style thing usually reserved for instance variables). – Michael Dautermann Nov 22 '15 at 13:12
  • Yea, it makes sense and I've done it. Check [this picture](http://i.imgur.com/Wo1LPt3.png) as it runs with no error, but... unfortunately it still shows me accesorry bar. [printscreen here](http://i.imgur.com/0osNpsR.jpg) . It just makes me sad after spending so many days figuring out a solution, and hitting a dead end. damn – Eduard Nov 22 '15 at 13:24
  • When I hit problems like this, I usually just use the Xcode debugger to step through the code and see if anything is unexpectedly nil (e.g. see if `targetView` or `newClass` are actually populated and used). You'll get the hang of things, eventually. – Michael Dautermann Nov 22 '15 at 14:48
  • I will mark this as solved as it helped me finish the conversion to swift. I'll try some debugging.. but , do tell me, is it ok as I applied it ? Thanks for the time you spent helping me – Eduard Nov 22 '15 at 21:13
  • It looks good to me so far. We are likely just missing something very small. – Michael Dautermann Nov 22 '15 at 21:55
  • Won't Apple reject this on App Store? – Paulo Cesar Mar 18 '16 at 13:09
  • @Eduard did you ever figure this out? Nothing seems to work. – JDev May 22 '17 at 23:28
  • @Matt Unfortunately no. I've quit trying. The solutions from above work in some extenr but it feels like a crappy experience, nothing smooth or anything. Went on, learned ReactJS, then moved to React Native :) – Eduard May 24 '17 at 04:11