Oleksandr Matrosov
Oleksandr Matrosov

Reputation: 27123

iOS 7 UIWebView keyboard issue

I have to remove this bar as here link but for iOS 7 this code does not work.

Upvotes: 13

Views: 7275

Answers (4)

Léo Natan
Léo Natan

Reputation: 57040

We remove this bar with some Objective C runtime trickery.

We have a class which has one method:

@interface _SwizzleHelper : NSObject @end

@implementation _SwizzleHelper

    -(id)inputAccessoryView
    {
        return nil;
    }

@end

Once we have a web view which we want to remove the bar from, we iterate its scroll view's subviews and take the UIWebDocumentView class. We then dynamically make the superclass of the class we created above to be the subview's class (UIWebDocumentView - but we cannot say that upfront because this is private API), and replace the subview's class to our class.

#import "objc/runtime.h"    

-(void)__removeInputAccessoryView
{
    UIView* subview;

    for (UIView* view in self.scrollView.subviews) {
        if([[view.class description] hasPrefix:@"UIWeb"])
            subview = view;
    }

    if(subview == nil) return;

    NSString* name = [NSString stringWithFormat:@"%@_SwizzleHelper", subview.class.superclass];
    Class newClass = NSClassFromString(name);

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

        Method method = class_getInstanceMethod([_SwizzleHelper class], @selector(inputAccessoryView));
        class_addMethod(newClass, @selector(inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method));

        objc_registerClassPair(newClass);
    }

    object_setClass(subview, newClass);
}

The equivalent of the above in Swift 3.0:

import UIKit
import ObjectiveC

var swizzledClassMapping = [AnyClass]()

extension UIWebView {
    func noInputAccessoryView() -> UIView? {
        return nil
    }

    public func removeInputAccessoryView() {
        var subview: AnyObject?

        for (_, view) in scrollView.subviews.enumerated() {
            if NSStringFromClass(type(of: view)).hasPrefix("UIWeb") {
                subview = view
            }
        }

        guard subview != nil else {
            return
        }

        //Guard in case this method is called twice on the same webview.
        guard !(swizzledClassMapping as NSArray).contains(type(of: subview!)) else {
            return;
        }

        let className = "\type(of: subview!)_SwizzleHelper"
        var newClass : AnyClass? = NSClassFromString(className)

        if newClass == nil {
            newClass = objc_allocateClassPair(type(of: subview!), className, 0)

            guard newClass != nil else {
                return;
            }

            let method = class_getInstanceMethod(type(of: self), #selector(UIWebView.noInputAccessoryView))
            class_addMethod(newClass!, #selector(getter: UIResponder.inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method))

            objc_registerClassPair(newClass!)

            swizzledClassMapping += [newClass!]
        }

        object_setClass(subview!, newClass!)
    }
}

Upvotes: 25

Pierre
Pierre

Reputation: 1053

I've made a cocoapod based on this blog post from @bjhomer.

You can replace the inputaccessoryview and not just hide it. I hope this will help people with the same issue.

https://github.com/lauracpierre/FA_InputAccessoryViewWebView

You can find the cocoapod page right here.

Upvotes: 1

kgaidis
kgaidis

Reputation: 15579

I created 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

You can either set it to nil to remove it or you can set a new view on it to change it.

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

Upvotes: 0

NKorotkov
NKorotkov

Reputation: 3681

I've came across this awesome solution, but I needed to get the inputAccessoryView back as well. I added this method:

- (void)__addInputAccessoryView {

    UIView* subview;

    for (UIView* view in self.scrollView.subviews) {

        if([[view.class description] hasSuffix:@"SwizzleHelper"])
            subview = view;
    }

    if(subview == nil) return;

    Class newClass = subview.superclass;

    object_setClass(subview, newClass);
}

It does seem to work as intended with no side effects, but I can't get rid of the feeling that my pants are on fire.

If you want Leo Natan's solution to work with WKWebView instead of UIWebView just change prefix from "UIWeb" to "WKContent".

Upvotes: 0

Related Questions