Modifying keyboard toolbar / accessory view with WKWebView
Asked Answered
G

2

9

I'm using a WKWebView with a content-editable div as the core of a rich-text editor, and would like to modify the top toolbar above the keyboard—the one that contains the autocorrect suggestions and formatting buttons. (Not sure if this counts as an input accessory view or not).

I've found a few posts showing how to remove the bar, but none of them seems to work, and ideally I'd like to keep the autocorrect part anyway.

At least one app, Ulysses, does this (though I don't know if it's with a web view):

Ulysses

And indeed, I'm pretty sure I can achieve it by doing surgery on the keyboard view hierarchy...but that seems like a tedious and brittle approach.

Is there a better way?

Thanks!

Glynis answered 25/2, 2018 at 20:28 Comment(4)
what did you try? did you try to define your own input accessory view?Horologist
Unfortunately WKWebView doesn’t expose an input accessory view, and the few workarounds for that that I’ve found online don’t seem to do anything.Glynis
I think this is very much possible. I have seen this in a library called IQKeyboardManager. It shows the arrow button along with a done button to hide keyboard. Perhaps you could check the code. github.com/hackiftekhar/IQKeyboardManagerGiffie
Thanks @Rishabh. Unfortunately I don't think IQKeyboardManager will help in this case: (a) it's designed to work with text fields and text views, but not web views; and (b) it places a bar above the keyboard, rather than replacing or modifying the keyboard's own bar (so you get two stacked bars).Glynis
T
8

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:

enter image description here

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.

Trometer answered 1/3, 2018 at 20:6 Comment(1)
Thanks for providing the answer. Unfortunately, as of 2024, it only works for iPad. The documentation of 'UITextInputAssistantItem' states, "You can add custom items to the shortcuts bar on iPad only. On iPhone, the text input system ignores the contents of the UITextInputAssistantItem object."Koontz
G
0

Perhaps a more stable (future-proof) way of having the same behavior would be to place the input accessory view in the WKWebView.superview, then use respond to keyboard events, to show and hide it. It's the old-fashioned way of making input accessory views.

Details:

  1. observe keyboard show/hide notifications
  2. place the input accessory view in the WKWebView.superview
  3. on keyboard show: position the input accessory view above the keyboard
  4. on keyboard hide: hide it with keyboard

And perhaps obvious, use delegation to pass action events between the WKWebView and your UIViewController/UIView subclass.

Also, in iOS 13+, you can override the inputAccessoryView. You should be able to replace it to provide your own on iPad. More details here: https://mcmap.net/q/956529/-removing-wkwebview-accesory-bar-in-swift

Gonnella answered 17/9, 2019 at 19:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.