6

I've been looking for days for a simple answer but I cannot seem to find it.

I want to find and replace the inputAccessoryView of keyboard attached to my UIWebView if the accessory view exists on the keyboard. I want to replace it so I can have my own Prev and Next buttons. Doing it in swift is a plus.

Sounds simple enough, but it's not obvious.

user3670534
  • 123
  • 1
  • 8
  • I do not believe this is easy to achieve. The following has some discussion and code links which may help you: http://stackoverflow.com/questions/19961369/how-the-default-keyboard-comes-up-when-user-taps-in-uiwebview – Rory McKinnel May 18 '15 at 21:22
  • I figured it out. I'll post the code here, but what I did was added some extensions to the UIViewController (although I probably could have added them to UIWebView instead), traversed the UIViews and any subviews until I found the view with the description " – user3670534 May 19 '15 at 15:52

4 Answers4

3
import UIKit

extension UIViewController {

    func addNewAccessoryView(oldAccessoryView:UIView)
    {
        var frame = oldAccessoryView.frame;
        var newAccessoryView = UIView(frame:frame)
        newAccessoryView.backgroundColor = UIColor.lightGrayColor()

        let fn = ".HelveticaNeueInterface-Bold"
        var doneButton = UIButton(frame:CGRectMake(frame.size.width-80,6,100,30))
        doneButton.setTitle("Done", forState: UIControlState.Normal)
        doneButton.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        doneButton.titleLabel!.font = UIFont(name: fn, size: 15)
        doneButton.addTarget(self, action: "buttonAccessoryDoneAction:", forControlEvents: UIControlEvents.TouchUpInside)

        var prevButton = UIButton(frame:CGRectMake(2,6,50,30))
        prevButton.setTitle("<", forState: UIControlState.Normal)
        prevButton.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        prevButton.titleLabel!.font = UIFont(name: fn, size: 15)
        prevButton.addTarget(self, action: "buttonAccessoryPrevAction:", forControlEvents: UIControlEvents.TouchUpInside)

        var nextButton = UIButton(frame:CGRectMake(35,6,50,30))
        nextButton.setTitle(">", forState: UIControlState.Normal)
        nextButton.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        nextButton.titleLabel!.font = UIFont(name: fn, size: 15)
        nextButton.addTarget(self, action: "buttonAccessoryNextAction:", forControlEvents: UIControlEvents.TouchUpInside)

        newAccessoryView.addSubview(prevButton);
        newAccessoryView.addSubview(nextButton);
        newAccessoryView.addSubview(doneButton);

        oldAccessoryView.addSubview(newAccessoryView)

    }

    func traverseSubViews(vw:UIView) -> UIView
    {
        if (vw.description.hasPrefix("<UIWebFormAccessory")) {
            return vw
        }

        for(var i = 0 ; i < vw.subviews.count; i++) {
            var subview = vw.subviews[i] as UIView;
            if (subview.subviews.count > 0) {
                var subvw = self.traverseSubViews(subview)
                if (subvw.description.hasPrefix("<UIWebFormAccessory")) {
                    return subvw
                }
            }
        }
        return UIView()
    }

    func replaceKeyboardInputAccessoryView()
    {
        // locate accessory view
        let windowCount = UIApplication.sharedApplication().windows.count
        if (windowCount < 2) {
            return;
        }

        let tempWindow:UIWindow = UIApplication.sharedApplication().windows[1] as UIWindow
        var accessoryView:UIView = traverseSubViews(tempWindow)
        if (accessoryView.description.hasPrefix("<UIWebFormAccessory")) {
            // Found the inputAccessoryView UIView
            if (accessoryView.subviews.count > 0) {
                self.addNewAccessoryView(accessoryView)
            }
        }
    }

    // Override these in your UIViewController
    func buttonAccessoryNextAction(sender:UIButton!)
    {
        println("Next Clicked")
    }

    func buttonAccessoryPrevAction(sender:UIButton!)
    {
        println("Prev Clicked")
    }

    func buttonAccessoryDoneAction(sender:UIButton!)
    {
        println("Done Clicked")
    }        
}

Then from inside your UIViewController setup a Notification when the keyboard appears. You can do this inside of viewDidAppear().

    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardDidShow:"), name:UIKeyboardDidShowNotification, object: nil);

and from within your keyboardDidShow handler call the replaceKeyboardInputAccessoryView method of the UIViewController.

func keyboardDidShow(notification: NSNotification) {
    if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
        self.replaceKeyboardInputAccessoryView()
    }
}
Rob
  • 4,927
  • 12
  • 49
  • 54
user3670534
  • 123
  • 1
  • 8
0

user3670534 solution works fine, but if the order of windows is changed you can use the following to get the correct window:

var tempWindow: UIWindow!;
for window in UIApplication.sharedApplication().windows {
    if (window.description.hasPrefix("<UITextEffectsWindow")) {
        tempWindow = window
    }
}
Alexander Scholz
  • 2,100
  • 1
  • 20
  • 35
0

I posted a gist to accomplish this: https://gist.github.com/kgaidis/5f9a8c7063b687cc3946fad6379c1a66

It's a UIWebView category where all you do is change the customInputAccessoryView property:

@interface UIWebView (CustomInputAccessoryView)

@property (strong, nonatomic) UIView *customInputAccessoryView;

@end

Keep in mind, this uses private API's, so use at your own risk, but it seems like a lot of apps do similar things nonetheless.

kgaidis
  • 14,259
  • 4
  • 79
  • 93
0

In Swift 3

I removed the built in Accessory view and then built an own view with XIB. (thanks @Rob) This shows howto remove the built-in view:

WebViewExtensions.swift:

import UIKit

extension UIViewController {
    func removeInputAccessoryView() {
        // locate accessory view
        let windowCount = UIApplication.shared.windows.count
        if (windowCount < 2) {
            return;
        }

        let tempWindow:UIWindow = UIApplication.shared.windows[1] as UIWindow
        let accessoryView:UIView = traverseSubViews(vw: tempWindow)
        if (accessoryView.description.hasPrefix("<UIWebFormAccessory")) {
            // Found the inputAccessoryView UIView
            accessoryView.removeFromSuperview()
        }
    }

    func traverseSubViews(vw:UIView) -> UIView
    {
        if (vw.description.hasPrefix("<UIWebFormAccessory")) {
            return vw
        }

        for i in (0  ..< vw.subviews.count) {
            let subview = vw.subviews[i] as UIView;
            if (subview.subviews.count > 0) {
                let subvw = self.traverseSubViews(vw: subview)
                if (subvw.description.hasPrefix("<UIWebFormAccessory")) {
                    return subvw
                }
            }
        }
        return UIView()
    }
}

WKWebView

override func viewDidLoad() {
        super.viewDidLoad()

        NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardDidShow, object: nil, queue: nil) { (notification) in                

            if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {                    
                self.removeInputAccessoryView()
            }
        }

    }

By using the solution above it's possible to modify this.

Daniel Nord
  • 535
  • 5
  • 10