Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resize IOS WKWebView when keyboard appears

I would like to resize a WKWebView to fit above the keyboard in both landscape and portrait and to cope with user changing between landscape and portrait whilst the keyboard is in use. I would also like the WKWebView to be resized when the keyboard disappears.

The default behaviour is to scroll the WKWebView so that the text field in use is visible above the keyboard. I want to override this behaviour.

I expect that I will have to add some code to respond to keyboardDidShow and to keyboardDidHide, to change the GCRect used by the WebView. Are these the correct events to use?

How do I access the height of the keyboard in both portrait and landscape?

Do I have to add a delay to this, or will the default scrolling of the webview have already occurred by the time keyboardDidShow fires?

When I change the webview GCRect size, will this trigger window.onresize in the javascript?

Will doing this allow the device to be rotated whilst the keyboard is in use or do I need to add further code to handle this?

Thanks for any help anyone can give me.

like image 364
Steve Brooker Avatar asked Oct 16 '19 18:10

Steve Brooker


Video Answer


1 Answers

To solve this I added the following declarations to ViewController.h

int appH ; // device width
int appW ; // device height
int keyH ; // keyboard height
int topMargin ; // safe area size at top of screen
int bottomMargin ; // safe area size at bottom of screen
bool smallScreen ; // whether want to see status bar in landscape

I expect that I should have declared these as properties of my ViewController object, but that is noting to do with the answer

I then create the web view as follows

- (void)viewDidAppear:(BOOL)animated
    {
    [super viewDidAppear:(BOOL)animated];

    NSString *deviceType = [UIDevice currentDevice].model;
    smallScreen = [deviceType hasPrefix:@"iPhone"];

    WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc] init];
    [theConfiguration.userContentController addScriptMessageHandler:self name:@"MyApp"];
    [theConfiguration.preferences setValue:@"TRUE" forKey:@"allowFileAccessFromFileURLs"];

    // note that in viewDidLoad safeAreaInsets is set to zero
    topMargin = [UIApplication sharedApplication].statusBarFrame.size.height ;
    if (@available(iOS 11, *)) topMargin = self.view.safeAreaInsets.top ;
    bottomMargin = 0 ;
    if (@available(iOS 11, *)) bottomMargin = self.view.safeAreaInsets.bottom ;

    int top    = (smallScreen && appW > appH)? 0    : topMargin ;
    int height = (smallScreen && appW > appH)? appH : appH - topMargin - bottomMargin ;

    webView = [[WKWebView alloc]initWithFrame:CGRectMake(0, top , appW, height) configuration:theConfiguration];
    webView.navigationDelegate = self;
    webView.UIDelegate = self;

I then react to changes in size as follows

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
    {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
    // code here executes before rotation
    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context)
        {
        // code here executes during rotation
        appH = size.height ;
        appW = size.width  ;
        int top    = (smallScreen && appW > appH)? 0    : topMargin ;
        int height = (smallScreen && appW > appH)? appH : appH - topMargin - bottomMargin ;
        webView.frame = CGRectMake(0, top , appW, height);
        }
     completion:^(id<UIViewControllerTransitionCoordinatorContext> context)
        {
        // code here executes after rotation
        [webView evaluateJavaScript:@"IOSOrientationChanged()" completionHandler:nil];
        }
     ];
    }

I also register for notification of keyboard events as follows

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

    [[NSNotificationCenter defaultCenter] addObserver:self
                                                selector:@selector(keyboardWillHide:)
                                                    name:UIKeyboardWillHideNotification
                                                  object:nil];

And react to these in this way

- (void)keyboardWillShow: (NSNotification *) notif
    {
    keyH = [notif.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height ;
    NSString *javascriptString ;
    javascriptString = [NSString stringWithFormat:@"IOSKeyboardChanged(%d)",keyH];
    [webView evaluateJavaScript:javascriptString completionHandler:nil];
    }

- (void)keyboardWillHide: (NSNotification *) notif
    {
    [webView evaluateJavaScript:@"IOSKeyboardChanged(0)" completionHandler:nil];
    }

In the above sections I have referred to two Javascript functions

IOSOrientationChanged redraws the app content based on the size of the screen found in window.innerWidth and window.innerHeight.

IOSKeyboard Changed does the same but uses window.innerHeight less the height of the keyboard (which is passed as the only parameter to it).

Points to note

  1. Do not react in javascript to window.onorientationchange or window.onresize
  2. I create a new frame for the web view on rotation but by doing this during the transition and then calling the javascript code on completion, it is good enough for me.
  3. I initially tried creating a new frame on completion, but this caused many problems for me due to the few milliseconds that this took and the need to add variable delays inside javascript to wait for it to happen.
  4. I also tried moving the new frame creation to before the rotation but this caused launch screen issues. Not shown above, I have a view inside view controller identical to my launch screen which is displayed on viewDidLoad to cover the time taken to load the html/css/javascript. This is removed when the javascript reports that it has been loaded.
  5. I do not need to create a new frame when the keyboard appears or is removed, I simply inform the javascript to not use the hidden area. I found creating new web view frames here had the same time delay issues as above.
  6. Note that this solution works when rotating the device with the keyboard is visible.

UPDATE IOS 13.3.1

After updating to this version, I found that the code above was insufficient to prevent the web view occasionally scrolling partially out of sight. Please note the "occasionally". Most of the time the code worked as it did previously, however the occasional issues were sufficient to warrant a fix and to do this I did the following :

  1. add to the ViewController interface statement

    UIScrollViewDelegate

  2. after creating the web view add the following line

    webView.scrollView.delegate = self ;

  3. also add

    • (void)scrollViewDidScroll:(UIScrollView *)scrollView

    { [scrollView setContentOffset:CGPointMake(0,0)]; };

like image 112
Steve Brooker Avatar answered Oct 17 '22 01:10

Steve Brooker