Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I animate shrinking of a uiwebview?

I'm trying to shrink a webview smoothly when the keyboard appears, but I'm having trouble with the animation. As soon as the animation is created, the webview's content shrinks to its new frame. After the animation delay, the webview itself animates properly to its new size.

The screenshots below show what it looks like. I've set the background of the web view's scroll view to yellow and the background of the view controller view to green.

1234

How can I have the content animate smoothly with the webView? This is the code I have for the animation.

-(void)shrinkWebView {
    __weak CDVPlugin* weakSelf = self;
    CGRect frame = weakSelf.webView.frame;
    frame.size.height -= 400;
    [UIView animateWithDuration:2.0
                          delay:3.0
                        options:0
                     animations:^{
                         weakSelf.webView.frame = frame;
                     }
                     completion:nil];
}

Thanks for the help

Here's the stripped down version of the cordova sample app I'm working with.

* {
    -webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */
}

body {
    -webkit-touch-callout: none;                /* prevent callout to copy image, etc when tap to hold */
    -webkit-text-size-adjust: none;             /* prevent webkit from resizing text to fit */
    -webkit-user-select: none;                  /* prevent copy paste, to allow, change 'none' to 'text' */
    background-color:#E4E4E4;
    background-image:linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
    background-image:-webkit-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
    background-image:-ms-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
    background-image:-webkit-gradient(
        linear,
        left top,
        left bottom,
        color-stop(0, #A7A7A7),
        color-stop(0.51, #E4E4E4)
    );
    background-attachment:fixed;
    font-family:'HelveticaNeue-Light', 'HelveticaNeue', Helvetica, Arial, sans-serif;
    font-size:12px;
    height:100%;
    margin:0px;
    padding:0px;
    text-transform:uppercase;
    width:100%;
}

/* Portrait layout (default) */
.app {
    background:url(../img/logo.png) no-repeat center top; /* 170px x 200px */
    position:absolute;             /* position in the center of the screen */
    left:50%;
    top:50%;
    height:50px;                   /* text area height */
    width:225px;                   /* text area width */
    text-align:center;
    padding:180px 0px 0px 0px;     /* image height is 200px (bottom 20px are overlapped with text) */
    margin:-115px 0px 0px -112px;  /* offset vertical: half of image height and text area height */
                                   /* offset horizontal: half of text area width */
}

/* Landscape layout (with min-width) */
@media screen and (min-aspect-ratio: 1/1) and (min-width:400px) {
    .app {
        background-position:left center;
        padding:75px 0px 75px 170px;  /* padding-top + padding-bottom + text area = image height */
        margin:-90px 0px 0px -198px;  /* offset vertical: half of image height */
                                      /* offset horizontal: half of image width and text area width */
    }
}

h1 {
    font-size:24px;
    font-weight:normal;
    margin:0px;
    overflow:visible;
    padding:0px;
    text-align:center;
}

.event {
    border-radius:4px;
    -webkit-border-radius:4px;
    color:#FFFFFF;
    font-size:12px;
    margin:0px 30px;
    padding:2px 0px;
}

.event.listening {
    background-color:#333333;
    display:block;
}

.event.received {
    background-color:#4B946A;
    display:none;
}

@keyframes fade {
    from { opacity: 1.0; }
    50% { opacity: 0.4; }
    to { opacity: 1.0; }
}
 
@-webkit-keyframes fade {
    from { opacity: 1.0; }
    50% { opacity: 0.4; }
    to { opacity: 1.0; }
}
 
.blink {
    animation:fade 3000ms infinite;
    -webkit-animation:fade 3000ms infinite;
}
<html>
    <head>
        <meta charset="utf-8" />
        <meta name="format-detection" content="telephone=no" />
        <meta name="msapplication-tap-highlight" content="no" />
        <!-- WARNING: for iOS 7, remove the width=device-width and height=device-height attributes. See https://issues.apache.org/jira/browse/CB-4323 -->
        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, target-densitydpi=device-dpi" />
        <link rel="stylesheet" type="text/css" href="css/index.css" />
        <title>Hello World</title>
    </head>
    <body>
        <div class="app">
            <button onclick="Keyboard.hideFormAccessoryBar(false)">Shrink</button>
            <input type="text" />
            <h1>Apache Cordova</h1>
            <div id="deviceready" class="blink">
            </div>
        </div>
        <script type="text/javascript" src="cordova.js"></script>
        <script type="text/javascript" src="js/index.js"></script>
    </body>
</html>
like image 891
Connor Avatar asked Jan 13 '15 13:01

Connor


2 Answers

What about resizing document.body.style.height via javascript?

- (void)resizeWebView:(UIWebView *)webView toHeight:(CGFloat)height duration:(CGFloat)duration {
    // We will wrap all webView contents with div and animate it's size
    // so webView content will resize independently of webView's frame.
    NSString *javascriptString = [NSString stringWithFormat:@""
            "function animate(elem, style, unit, from, to, time) {\n"
            "    var start = new Date().getTime(),\n"
            "        timer = setInterval(function() {\n"
            "            var step = Math.min(1,(new Date().getTime()-start)/time);\n"
            "            elem.style[style] = (from+step*(to-from))+unit;\n"
            "            if( step == 1) clearInterval(timer);\n"
            "        }, 25);\n"
            "    elem.style[style] = from+unit;\n"
            "}\n"
            "document.body.style.position = 'relative';\n"
            "animate(document.body, 'height', 'px', document.body.offsetHeight, %f, %f)",
        height, duration*1000];
    [webView stringByEvaluatingJavaScriptFromString:javascriptString];

    BOOL isGrowing = height > webView.frame.size.height;

    // If webView is growing, change it's frame imediately, so it's content not clipped during animation
    if (isGrowing) {
        CGRect frame = webView.frame;
        frame.size.height = height;
        webView.frame = frame;
    }

    __weak typeof(webView) weakWebView = webView;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        __strong typeof(weakWebView) webView = weakWebView;
        // If webview was shrinking, change it's frame after animation is complete
        if (!isGrowing) {
            CGRect frame = webView.frame;
            frame.size.height = height;
            webView.frame = frame;
        }
        // Remove remove body position constraints
        [webView stringByEvaluatingJavaScriptFromString:@""
                "document.body.style.position = document.body.style.height = null;"
        ];
    });
}

- (void)toggleButtonTapped:(id)sender {
     [self resizeWebView:self.webView toHeight:300 duration:0.4];
}

Example

Edit: Updated code, now all html is wrapped with <div style="position:relative;">, so absolutely positioned elements are animated too.

Edit 2: Ok, manipulation DOM structure wasn't good idea – if you were trying to resize webView when user focuses input inside webView and keyboard appears, input was loosing focus and keyboard was immediately hidden. So now I just add document.body.style.position = 'relative' to code and resize body.

I also created category on UIWebView, you can install it via cocoapods.

like image 129
glyuck Avatar answered Nov 02 '22 13:11

glyuck


Here is a sample code.

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIWebView *webView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *webViewBottomConstraint;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.google.com"]]];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyBoardShow:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyBoardShow:) name:UIKeyboardWillChangeFrameNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyBoardHide:) name:UIKeyboardWillHideNotification object:nil];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - Keyboard Notification
-(void)keyBoardShow:(NSNotification*)noti
{

    CGRect keyboardBounds;
    [[noti.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue: &keyboardBounds];
    NSNumber *duration = [noti.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];

    // Need to translate the bounds to account for rotation.
    keyboardBounds = [self.view convertRect:keyboardBounds toView:nil];

    [UIView animateWithDuration:duration.floatValue animations:^{
        [self.webViewBottomConstraint setConstant:keyboardBounds.size.height];
        [self.view layoutIfNeeded];
    }];

}

-(void)keyBoardHide:(NSNotification*)noti
{

    CGRect keyboardBounds;
    [[noti.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue: &keyboardBounds];
    NSNumber *duration = [noti.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];

    [UIView animateWithDuration:duration.floatValue animations:^{
        [self.webViewBottomConstraint setConstant:0];
        [self.view layoutIfNeeded];
    }];

}

@end

Hopes it could help you.

like image 40
Steven Jiang Avatar answered Nov 02 '22 14:11

Steven Jiang