UIWebView用于在App中嵌入網(wǎng)頁(yè)內(nèi)容,通常情況下是html格式的網(wǎng)頁(yè),也支持pdf, word等文檔。
首先讓我們了解一下UIWebView有哪些優(yōu)點(diǎn):
可跨平臺(tái)
開發(fā)一次可以部署iOS、Android等平臺(tái)。
發(fā)布更新快
在服務(wù)器端發(fā)布,能夠?qū)崟r(shí)更新終端展示,便于快速升級(jí)以及緊急修復(fù)bug。
排版布局能力強(qiáng)
強(qiáng)大的HTML+CSS讓人膜拜
世界上有十全十美的人么?也許只有上帝吧。UIWebView的缺點(diǎn):
性能
Native先生與HTML5先生爭(zhēng)論時(shí)最喜歡說(shuō)的一句話就是:“你性能不行”(看清楚了哈,不是“性能力不行”)。Web App運(yùn)行在瀏覽器里,目前瀏覽器的開放能力難以支持HTML5與Native對(duì)抗。
數(shù)據(jù)通訊復(fù)雜
UIWebView與App之間進(jìn)行數(shù)據(jù)通訊只能通過(guò)javascript或者UIWebViewDelegate來(lái)進(jìn)行,客戶端想傳參數(shù)給UIWebView修改網(wǎng)頁(yè)或者從網(wǎng)頁(yè)中獲取數(shù)據(jù)都比較復(fù)雜。
咱們應(yīng)該揚(yáng)長(zhǎng)避短,在以下場(chǎng)景考慮使用UIWebView:
排版復(fù)雜的內(nèi)容
圖文混排、文字環(huán)繞、文章內(nèi)各種超鏈及高亮顯示。這些讓iOS工程師抓狂的界面,讓web前端小伙伴們搞定,吃頓飯,然后用UIWebView包起來(lái)。
需后臺(tái)靈活控制的界面
認(rèn)證、免責(zé)聲明以及PM要求三天兩頭需要變幻莫測(cè)的頁(yè)面等。怎么辦?告訴我地址,UIWebView包起來(lái),結(jié)束:)
原網(wǎng)頁(yè)
查看新浪網(wǎng)、騰訊網(wǎng)等網(wǎng)頁(yè)內(nèi)容,這個(gè)木有辦法,總不能讓我們自己寫一個(gè)瀏覽器功能吧。
UIWebView的常規(guī)使用方法:
加載內(nèi)容
//加載網(wǎng)頁(yè)或者本地文件
- (void)loadRequest:(NSURLRequest *)request;
//直接加載html內(nèi)容,如果html中的圖片等資源在本地目錄,注意將baseURL指向該目錄
- (void)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL;
//功能與上面類似
- (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName baseURL:(NSURL *)baseURL;
實(shí)現(xiàn)UIWebViewDelegate
主要使用到的方法
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
//在這里截取網(wǎng)絡(luò)請(qǐng)求,可以為所欲為的做你想做的事情:)
//…
}
使用stringByEvaluatingJavaScriptFromString與UIWebView中的網(wǎng)頁(yè)進(jìn)行數(shù)據(jù)通訊,需要你有點(diǎn)javascript功底,也可以去看看js bridge的相關(guān)文章。
上面說(shuō)的東西是不是太俗套,太正統(tǒng)了?下面講點(diǎn)實(shí)際點(diǎn)兒的吧。
科普一下:
UIWebView包含著一個(gè)scrollView,iOS5的時(shí)候已經(jīng)公開,在此之前需寫代碼遍歷UIWebView的subviews把它找出來(lái)。scrollView里面包含著一個(gè)UIWebBrowserView用于渲染網(wǎng)頁(yè)內(nèi)容的,該屬性沒(méi)有公開,需要遍歷其subviews找出來(lái)。
去掉拖動(dòng)到頂部或者底部時(shí)露出來(lái)的漸變顏色
如圖所示:
image 解決辦法:
將scrollView中包含的所有UIImageView隱藏
- (void)removeGradientBgColorOfWebView:(UIWebView*)aWebView{
NSArray *subViews = aWebView.subviews;
for (UIView* subView in subViews){
if ([subView isKindOfClass:[UIScrollView class]]) {
for (UIView* shadowView in [subView subviews]){
if ([shadowView isKindOfClass:[UIImageView class]]) {
[shadowView setHidden:YES];
}
}
}
}
}
結(jié)果:
image
白屏閃爍
你嵌入的html內(nèi)容是有背景色的,但是在load的時(shí)候還是會(huì)有白屏閃爍,在展現(xiàn)你的內(nèi)容前會(huì)出現(xiàn)白色背景,任憑你怎么設(shè)置UIWebView或者里面scrollView, webBrowserView的backgroundColor都沒(méi)有作用。解決辦法:
//在load之前,先設(shè)置兩個(gè)屬性
_webView.opaque = NO;
[_webView.scrollView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSString *ss = [NSString stringWithUTF8String:object_getClassName(obj)];
if ([ss isEqualToString:@"UIWebBrowserView"] ) {
[obj setHidden:YES];
*stop = YES;
}
}];
[_webView loadHTMLString:htmlStr baseURL:baseURL];
在UIWebViewDelegate中實(shí)現(xiàn)如下:
#pragma mark - UIWebViewDelegate
- (void)webViewDidStartLoad:(UIWebView *)webView{
[_webView.scrollView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSString *ss = [NSString stringWithUTF8String:object_getClassName(obj)];
if ([ss isEqualToString:@"UIWebBrowserView"] ) {
[obj setHidden:NO];
*stop = YES;
}
}];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView{
_webView.opaque = YES;
_webView.backgroundColor = …;
}
在網(wǎng)頁(yè)頂部加上一個(gè)headerView,并隨著網(wǎng)頁(yè)一起滾動(dòng)
由于需要一起滾動(dòng),所以footerView應(yīng)該是_webView.scrollView的subview。上面我們提到過(guò)_webView.scrollView.webBrowserView是用來(lái)渲染網(wǎng)頁(yè)的view,所以我們的headerView只要在webBrowserView上面即可。
[_webView.scrollView addSubview:_headerView];
UIView *webBrowserView = [_webView webBrowserView];
CGRect frame = webBrowserView.frame;
frame.origin.y = CGRectGetMaxY(_headerView.frame);
webBrowserView.frame = frame;
注意:在scrollView頂部添加headerView,并且正確設(shè)置了webBrowserView的位置后不會(huì)影響到下面3中的contentSize計(jì)算,UIWebView應(yīng)該已經(jīng)考慮到了webBrowserView的位置偏移。
下面紅色區(qū)域即是headerView
image
在網(wǎng)頁(yè)的末尾加上一個(gè)footerView,并且跟著網(wǎng)頁(yè)一塊滾動(dòng)
由于需要一起滾動(dòng),所以footerView應(yīng)該是_webView.scrollView的subview。正常情況下你是不知道網(wǎng)頁(yè)被渲染后的高度的,也就是說(shuō)不知道_webView.scrollView.contentSize,如果知道contentSize就好辦了,直接將footerView添加在末尾。
咱們可以利用KVO來(lái)解決這個(gè)問(wèn)題。
//監(jiān)聽(tīng)scrollView的contentSize的變化
- (void)webViewDidFinishLoad:(UIWebView *)webView{
[self addObserverForWebViewContentSize];
//0.1s后設(shè)置footerView的位置,以防止contentSize沒(méi)有變化
[self performSelector:@selector(layoutFooterView) withObject:nil afterDelay:0.1];
}
有同學(xué)不禁要問(wèn):為什么需要監(jiān)聽(tīng)contentSize的變化呢?在webViewDidFinishLoad中直接取_webView.scrollView.contentSize不就可以了嗎?
解答:webViewDidFinishLoad會(huì)被多次回調(diào),因?yàn)榫W(wǎng)頁(yè)中有圖片、表格等多種資源,UIWebView在加載資源的時(shí)候會(huì)不斷調(diào)整contentSize以渲染新加載完成的內(nèi)容。
//contentSize變化時(shí),重新布局footerView
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if (context == &kContentSizeFlag) {
[self layoutFooterView];
}
}
- (void)addObserverForWebViewContentSize{
[_webView.scrollView addObserver:self forKeyPath:@“contentSize” options:0 context:&kContentSizeFlag];
}
- (void) removeObserverForWebViewContentSize{
[_webView.contentScrollView removeObserver:self forKeyPath:@“contentSize”];
}
//設(shè)置footerView的合理位置
- (void)layoutFooterView{
//取消監(jiān)聽(tīng),因?yàn)檫@里會(huì)調(diào)整contentSize,避免無(wú)限遞歸
[self removeObserverForWebViewContentSize];
CGSize contentSize = _webView.scrollView.contentSize;
CGFloat y = CGRectGetMaxY(_webView.webBrowserView.frame);
//設(shè)置footerView的位置
_footerView.frame = CGRectMake(0, y, contentSize.width, footerHeight);
[_webView.scrollView addSubview:_footerView];
_webView.scrollView.contentSize = CGSizeMake(contentSize.width, y + footerHeight);
//重新監(jiān)聽(tīng)
[self addObserverForWebViewContentSize];
}
//下圖底部紅色區(qū)域即footerView
image
滑動(dòng)隱藏頂部Bar
如果你想在用戶向上滑動(dòng)時(shí)隱藏頂部bar,向下滑動(dòng)時(shí)顯示頂部bar,該怎么辦呢?
_webView.scrollView的delegate是_webView自身,你是不能接管的,所以拿不到scrollView的相關(guān)事件。
怎么辦呀???
好吧,答案就是:KVO, contentOffset
- (void)hideNavigationBar:(BOOL)animated { … }
- (void)showNavigationBar:(BOOL)animated { … }
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if(context == &kContentOffsetFlag){
CGFloat y = [object contentOffset].y;
UIScrollView *scrollView = _webView.scrollView;
CGPoint p = [scrollView.panGestureRecognizer velocityInView:scrollView];
CGFloat maxY = scrollView.contentSize.height + scrollView.contentInset.top - scrollView.bounds.size.height;
if(fabsf(p.y) < 0.001 && _contentOffsetY - y > 5 && y < maxY - 5){
[self showNavigationBar:YES];
}
else if(p.y < 0 && _webView.scrollView.dragging) {//上滑隱藏
[self hideNavigationBar:YES];
}
else if(p.y > 1500){//快速下滑顯示
[self showNavigationBar:YES];
}
else if(y <= self.navigationBar.bounds.size.height){
[self showNavigationBar:NO];
}
}