当前位置 : 主页 > 手机开发 > 其它 >

使用UIWebView开发hybrid应用(一)

来源:互联网 收集:自由互联 发布时间:2021-06-12
鉴于目前iOS手机性能的提升,使用webview方法开发的app效果已经很理想,团队在项目中使用了hybrid的开发模式,积累了一定的经验,便分享出来给大家参考,并互相学习。 好了,废话不

鉴于目前iOS手机性能的提升,使用webview方法开发的app效果已经很理想,团队在项目中使用了hybrid的开发模式,积累了一定的经验,便分享出来给大家参考,并互相学习。

好了,废话不多说,下面从目标、技术原理、代码展示三个部分讲解。

一、目标

使用native与HTML结合的方式,变动小的页面使用native开发,如个人信息;而电影、影院信息等页面使用HTML(这个应用是买电影票的~)。为了使HTML页面的效果好一点,甚至接近原生的效果,需要对webview进行一系列的优化,于是苦逼的事情来啦···


二、优化手段

其实目前许多流行的应用,特别是数据庞大的应用(如淘宝、京东等)会采用的hybrid方式,看他们移动端的页面就能看出一些端倪。在页面加载时,几乎和app加载的效果差不多,先加载出整体的框架,然后填充页面中的数据,加载完后不就是个app嘛。


说了这么多,只想表明优化的关键在前端页面。如果在webview页面中加载的是淘宝的链接,体验会非常棒;然而加载自家的app,几秒钟过去了,却还是空白~,有种泪奔的感觉。

既然上面提出了优化的需求,我这个iOS程序猿总不能把HTML页面框架重写一遍,只好在webview上做文章:

1、支持右滑后退功能

2、上下拖动时,navigationBar对应隐藏和显示

3、缓存

下面分别介绍这些方法。


三、右滑后退功能

这是从CocoaChina上看到的现成例子,直接拿来用了,详见 仿微信内嵌网页

该demo中有个缺陷,右滑后退后页面会重载,不能像原生那样就稳定到滑动过程中看到页面,而自己目前没想到解决方案。

右滑后退功能用到了截屏、手势识别、自定义视图的变换。在页面加载后会截屏,并保存到图片队列中。进入子页面后,如果有右滑操作,则回调相应方法,让webview右偏并逐渐隐藏,而之前页面的截图随机出现。具体的原理可看demo。


四、上下拖动的效果

webview页面展示时,顶部的导航栏占据了一定区域,也不能提供什么有价值的信息,将它隐藏起来会使页面看着更大(biger than biger)。

如果在webview上加上上下拖动识别,发现拖动时并不能接收到回调信息。自己当时想了好久也不知咋办,于是了解了webview的原理,

NS_CLASS_AVAILABLE_IOS(2_0) __TVOS_PROHIBITED @interface UIWebView : UIView <NSCoding, UIScrollViewDelegate> 
原来webview中使用了scrollView的协议,上下拖动会被scrollview截获,用于显示页面信息,而不能触发手势识别。此时,在自定义类中使用UIScrollViewDelegate,就能接收到拖动时的信息,程序根据回调信息显示和隐藏navigationBar。

使用UIScrollViewDelegate需要重写scrollViewWillBeginDragging、scrollViewDidScroll、scrollViewDidEndDragging三个方法。开始拖动时,记录下scrollView的偏移(scrollView.contentOffset);在拖动过程中,会返回当前的偏移值,根据当前值和初始值,确定偏移量。navigationBar根据偏移量上下偏移,向上隐藏时,设置alpha值逐渐降低,直至透明。navigationBar向下偏移显示时,alpha升高,最后完全不透明;拖动结束后,根据偏移量确定navigationBar的最终状态,完全隐藏或者完全显示。这就是原理,以下是实现代码。

#pragma mark scrollView Delegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{

    scrollOffset = scrollView.contentOffset;
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    
    if (self.tabBarController.tabBar.hidden) {
        
        //往上拖,offset.y为正;
        CGPoint offset = scrollView.contentOffset;
        if (scrollView.contentSize.height <= webView.frame.size.height + defHeight) {
            return;
        }
        
        //在拖动过程中,超过NAVIGATION_BAR_HEIGHT的距离后,默认为44
        if (fabs(offset.y - scrollOffset.y) > defHeight) {
            
            if (offset.y > scrollOffset.y) {
                offset.y = defHeight + scrollOffset.y;
            }else if (offset.y < scrollOffset.y){
                offset.y = scrollOffset.y - defHeight;
            }
        }
        
        //拖动完成后
        //navBar隐藏后,往上拖则不响应
        if ((navBarState == NavBarStateHide) && (offset.y > scrollOffset.y) ) {
            return;
            //navBar显示后,往下拖则不响应
        }else if (navBarState == NavBarStateShow && offset.y < scrollOffset.y){
            return;
        }
        
        //返回后,页面已拖到底部,往下拖则不响应
        if (scrollOffset.y >= scrollView.contentSize.height - webView.frame.size.height - defHeight/2 && navBarState == NavBarStateShow) {
            return;
        }
        
        [self setNavigationBarFrame:(offset.y - scrollOffset.y)];
        
    }
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{

    if (!self.tabBarController.tabBar.hidden) {
        return;
    }
    
    CGPoint offset = scrollView.contentOffset;
    
    //navBar隐藏后,网页在上面隐藏的高度小于nav的高度时,往下拖动的距离大于5,则显示navBar
    if (navBarState == NavBarStateHide && offset.y < scrollOffset.y - 5) {
        [self navBarShowState];
        navBarState = NavBarStateShow;
        return;
    }

    //如果页面的大小 < webView的大小,则不响应
    if (scrollView.contentSize.height <= webView.frame.size.height + 44) {
        return;
    }
    
    //navBar隐藏后,往上拖则不响应
    if ((navBarState == NavBarStateHide) && (offset.y > scrollOffset.y) ) {
        return;
        //navBar显示后,往下拖则不响应
    }else if (navBarState == NavBarStateShow && offset.y < scrollOffset.y){
        return;
    }
    
    CGFloat distance = scrollOffset.y - offset.y;
    //移动距离超过22,则状态改变,否则不改变
    if (fabs(distance) > defHeight/2) {
        switch (navBarState) {
            case NavBarStateShow:
                [self navBarHideState];
                navBarState = NavBarStateHide;
                break;
                
            case NavBarStateHide:
                [self navBarShowState];
                navBarState = NavBarStateShow;
                break;
                
            default:
                break;
        }
    }else{
    
        switch (navBarState) {
            case NavBarStateShow:
                [self navBarShowState];
                navBarState = NavBarStateShow;
                break;
                
            case NavBarStateHide:
                [self navBarHideState];
                navBarState = NavBarStateHide;
                break;
                
            default:
                break;
        }
    }
    
}

/**
 *  拖动过程中,更改页面
 *
 *  @param y 拖动的距离,向上拖为正,向下拖为负
 */
- (void)setNavigationBarFrame:(CGFloat) y{
    
    CGRect webViewRect;
    CGRect navBarRect;
    
    switch (navBarState) {
        case NavBarStateShow:

            self.navigationController.navigationBar.alpha = 1 - y/44;
             webViewRect = initWebViewRect;
             navBarRect = initNavBarRect;
            break;
            
        case NavBarStateHide:

            [self.navigationController setNavigationBarHidden:NO animated:NO];
            self.navigationController.navigationBar.alpha = fabs(y/44);
            webViewRect = hidedWebViewRect;
            navBarRect = hidedNavBarRect;
            break;
            
        default:
            break;
    }

    webViewRect.size.height += y;
    webView.frame = CGRectOffset(webViewRect, 0, -y);
    self.navigationController.navigationBar.frame = CGRectOffset(navBarRect, 0, -y);
}

- (void)navBarShowState{
    
    webView.frame = initWebViewRect;
    [self.navigationController setNavigationBarHidden:NO animated:NO];
    self.navigationController.navigationBar.frame = initNavBarRect;
    self.navigationController.navigationBar.alpha = 1.0;
}

- (void)navBarHideState{
    
    webView.frame = hidedWebViewRect;
    [self.navigationController setNavigationBarHidden:YES animated:NO];
    self.navigationController.navigationBar.frame = hidedNavBarRect;
}

五、缓存

目前缓存部分的代码已经完成,但还在完善中,等写好了再贴上代码。

先简单说下原理。

最初的想法是把图片存到本地,但是想使用本地的缓存图片,则必须把HTML文件中的图片地址设为本地的。经过思考后,决定将HTML、css、js等所有文件加载到本地保存,对HTML文件进行解析,找出需要下载的文件,异步缓存到本地。使用CoreData保存每个页面的信息,如页面的URL地址,保存在文件中的路径,图片、css等文件是否下载完成。这部分代码折腾了一个多星期,挺麻烦的。在初次加载时,每下载完一个页面,webview便刷新,给人感觉不连贯。而且当一个页面中需要下载的文件过多时,花费的时间比直接用webview长很多。


这一节先写这么多,有进展了再补充下一姐。

网友评论