iOS 7 Uiwebview Keyboard Issue

iOS 7 UIWebView keyboard issue

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!)
}
}

Textfield does not focus in UI WebView in iOS7

Finally found the reason for the problem. If you are using UIWebview then you should set the min-height of the body tag to the inner height of the device. For example 460px for iPhone 4S or 520px for iPhone 5

Inner Height = Device height - Status bar height = 460px which should basically be the height of the app container. I put this piece of code in my phone gap device ready function & everything worked like a charm

document.addEventListener("deviceready", function(){
var body = document.getElementsByTagName('body')[0];
body.style.minHeight=window.innerHeight + 'px';
}, false);

Keyboard gets dismissed when tapping into UIWebViews

As it turns out, after scrounging the internet for a week for this and related problems, I ended up finding a duplicate question! I'm reposting the relevant code from this post by diegoreymendez.

UIWebView+GUIFixes.h

#import <UIKit/UIKit.h>

@interface UIWebView (GUIFixes)

/**
* @brief Wether the UIWebView will use the fixes provided by this category or not.
*/
@property (nonatomic, assign, readwrite) BOOL usesGUIFixes;

@end

UIWebView+GUIFixes.m

#import "UIWebView+GUIFixes.h"
#import <objc/runtime.h>

@implementation UIWebView (GUIFixes)

static const char* const fixedClassName = "UIWebBrowserViewMinusAccessoryView";
static Class fixClass = Nil;

- (UIView *)browserView
{
UIScrollView *scrollView = self.scrollView;

UIView *browserView = nil;
for (UIView *subview in scrollView.subviews) {
if ([NSStringFromClass([subview class]) hasPrefix:@"UIWebBrowserView"]) {
browserView = subview;
break;
}
}

return browserView;
}

- (BOOL)delayedBecomeFirstResponder
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[super becomeFirstResponder];
});

return YES;
}

- (void)ensureFixedSubclassExistsOfBrowserViewClass:(Class)browserViewClass
{
if (!fixClass) {
Class newClass = objc_allocateClassPair(browserViewClass, fixedClassName, 0);
objc_registerClassPair(newClass);

IMP delayedFirstResponderImp = [self methodForSelector:@selector(delayedBecomeFirstResponder)];
Method becomeFirstResponderMethod = class_getInstanceMethod(browserViewClass, @selector(becomeFirstResponder));
method_setImplementation(becomeFirstResponderMethod, delayedFirstResponderImp);

fixClass = newClass;
}
}

- (BOOL)usesGUIFixes
{
UIView *browserView = [self browserView];
return [browserView class] == fixClass;
}

- (void)setUsesGUIFixes:(BOOL)value
{
UIView *browserView = [self browserView];
if (browserView == nil) {
return;
}

[self ensureFixedSubclassExistsOfBrowserViewClass:[browserView class]];

if (value) {
object_setClass(browserView, fixClass);
}
else {
Class normalClass = objc_getClass("UIWebBrowserView");
object_setClass(browserView, normalClass);
}

[browserView reloadInputViews];
}

@end

It feels dirty and fragile having to deal with the objc runtime like this, but as far as I've found, its the only way that actually works.

UIKeyboardAppearance in UIWebView

A very easy solution would be to add a - (UIKeyboardAppearance) keyboardAppearance method via category extension to UIView. In this method you could simply return UIKeyboardAppearanceDark. This works because the method would magically be added to the internal UIWebView view (UIWebBrowserView) which becomes the first responder when the user taps into a HTML form input field. The main problem with this approach is that it will affect all UIView-derived views, which may not be desirable.

We can construct a more targeted solution which intercepts the first responder that is responsible for the keyboard and adds a keyboardAppearance method to it if one doesn't exist. This will degrade gracefully if the internal implementation of UIWebBrowserView changes in the future to include a keyboardAppearance selector.

#import <objc/runtime.h>

@protocol TSPingPong <NSObject>
- (void) ts_pong: (id) sender;
@end

@interface NSObject (TSPingPong)
@end
@implementation NSObject (TSPingPong)
- (void) ts_ping: (id) sender
{
if ( [sender respondsToSelector: @selector(ts_pong:)] )
{
[sender performSelector: @selector( ts_pong: ) withObject: self ];
}
}
@end

@implementation TSViewController
{
IBOutlet UIWebView* _webView;
}

- (void)viewDidLoad
{
[super viewDidLoad];

[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(keyboardWillAppear:)
name: UIKeyboardWillShowNotification
object: nil];

NSString* html = @"<br><br><br><form action=''> " \
"First name: <input type='text'><br> " \
"Last name: <input type='text'><br>" \
"<input type='submit' value='Submit'> " \
"</form>";

[_webView loadHTMLString: html
baseURL: nil];
}

- (void) keyboardWillAppear: (NSNotification*) n
{
// the keyboard is about to appear!
// play pingpong with the first responder so we can ensure it has a keyboardAppearance method:

[[UIApplication sharedApplication] sendAction: @selector( ts_ping:) // added via category extension
to: nil // to: the first responder
from: self // "sender"
forEvent: nil];
}

- (void) ts_pong: (id) sender
{
// sender is the first responder. Happens to be undocumented "UIWebBrowserView" in this case.

// if it doesn't have it's own keyboardAppearance method then let's add one:
if ( ![sender respondsToSelector: @selector(keyboardAppearance)] )
{
Method m = class_getInstanceMethod( [self class], @selector( keyboardAppearanceTemplateMethod ) );

IMP imp = method_getImplementation( m );

const char* typeEncoding = method_getTypeEncoding( m );

class_addMethod( [sender class], @selector(keyboardAppearance), imp, typeEncoding );
}
}

- (UIKeyboardAppearance) keyboardAppearanceTemplateMethod
{
return UIKeyboardAppearanceDark;
}

@end

scalesPageToFit broken on UIWebView after keyboard is shown?

Seems to be a bug with Apple's private class UIDocumentView. For what ever reason viewportConfigurationsDidChange gets stuck on a zoom scale after the keyboard is activated, which is set by calling _setDocumentScale. When it's supposed to change to 1.333, for example, it gets stuck on 1.0.

It's "fixable" by writing a category for UIWebDocumentView, which is a subclass of UIDocumentView that doesn't implement these methods (so you can call the super's).

Despite my best efforts I couldn't find a way to fix this using the public API. I tried messing with the UIScrollView's zoom level, but I could never get that to work correctly. The content would get shifted, and other weird things would happen. Even tried doing it with JavaScript.

So the only way to fix it is through category on private classes.

This would never be approved in the app store.

So until Apple fixes this, SOL.

How to remove inputAccessoryView of UIWebView in iOS7?

See my answer here. Our app uses this method and is in the store.

I dynamically create a class that is a subclass of UIWebDocumentView that has an implementation of inputAccessoryView which returns nil.



Related Topics



Leave a reply



Submit