UIWebView用于在App中嵌入網頁內容,通常情況下是html格式的網頁,也支持pdf, word等文檔。
首先讓我們了解一下UIWebView有哪些優點:
可跨平臺
開發一次可以部署iOS、Android等平臺。
發布更新快
在服務器端發布,能夠實時更新終端展示,便于快速升級以及緊急修復bug。
排版布局能力強
強大的HTML+CSS讓人膜拜
世界上有十全十美的人么?也許只有上帝吧。UIWebView的缺點:
性能
Native先生與HTML5先生爭論時最喜歡說的一句話就是:“你性能不行”(看清楚了哈,不是“性能力不行”)。Web App運行在瀏覽器里,目前瀏覽器的開放能力難以支持HTML5與Native對抗。
數據通訊復雜
UIWebView與App之間進行數據通訊只能通過javascript或者UIWebViewDelegate來進行,客戶端想傳參數給UIWebView修改網頁或者從網頁中獲取數據都比較復雜。
咱們應該揚長避短,在以下場景考慮使用UIWebView:
排版復雜的內容
圖文混排、文字環繞、文章內各種超鏈及高亮顯示。這些讓iOS工程師抓狂的界面,讓web前端小伙伴們搞定,吃頓飯,然后用UIWebView包起來。
需后臺靈活控制的界面
認證、免責聲明以及PM要求三天兩頭需要變幻莫測的頁面等。怎么辦?告訴我地址,UIWebView包起來,結束:)
原網頁
查看新浪網、騰訊網等網頁內容,這個木有辦法,總不能讓我們自己寫一個瀏覽器功能吧。
UIWebView的常規使用方法:
加載內容
//加載網頁或者本地文件
- (void)loadRequest:(NSURLRequest *)request;
//直接加載html內容,如果html中的圖片等資源在本地目錄,注意將baseURL指向該目錄
- (void)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL;
//功能與上面類似
- (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName baseURL:(NSURL *)baseURL;
實現UIWebViewDelegate
主要使用到的方法
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
//在這里截取網絡請求,可以為所欲為的做你想做的事情:)
//…
}
使用stringByEvaluatingJavaScriptFromString與UIWebView中的網頁進行數據通訊,需要你有點javascript功底,也可以去看看js bridge的相關文章。
上面說的東西是不是太俗套,太正統了?下面講點實際點兒的吧。
科普一下:
UIWebView包含著一個scrollView,iOS5的時候已經公開,在此之前需寫代碼遍歷UIWebView的subviews把它找出來。scrollView里面包含著一個UIWebBrowserView用于渲染網頁內容的,該屬性沒有公開,需要遍歷其subviews找出來。
去掉拖動到頂部或者底部時露出來的漸變顏色
如圖所示:
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];
}
}
}
}
}
結果:
image
白屏閃爍
你嵌入的html內容是有背景色的,但是在load的時候還是會有白屏閃爍,在展現你的內容前會出現白色背景,任憑你怎么設置UIWebView或者里面scrollView, webBrowserView的backgroundColor都沒有作用。解決辦法:
//在load之前,先設置兩個屬性
_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中實現如下:
#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 = …;
}
在網頁頂部加上一個headerView,并隨著網頁一起滾動
由于需要一起滾動,所以footerView應該是_webView.scrollView的subview。上面我們提到過_webView.scrollView.webBrowserView是用來渲染網頁的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,并且正確設置了webBrowserView的位置后不會影響到下面3中的contentSize計算,UIWebView應該已經考慮到了webBrowserView的位置偏移。
下面紅色區域即是headerView
image
在網頁的末尾加上一個footerView,并且跟著網頁一塊滾動
由于需要一起滾動,所以footerView應該是_webView.scrollView的subview。正常情況下你是不知道網頁被渲染后的高度的,也就是說不知道_webView.scrollView.contentSize,如果知道contentSize就好辦了,直接將footerView添加在末尾。
咱們可以利用KVO來解決這個問題。
//監聽scrollView的contentSize的變化
- (void)webViewDidFinishLoad:(UIWebView *)webView{
[self addObserverForWebViewContentSize];
//0.1s后設置footerView的位置,以防止contentSize沒有變化
[self performSelector:@selector(layoutFooterView) withObject:nil afterDelay:0.1];
}
有同學不禁要問:為什么需要監聽contentSize的變化呢?在webViewDidFinishLoad中直接取_webView.scrollView.contentSize不就可以了嗎?
解答:webViewDidFinishLoad會被多次回調,因為網頁中有圖片、表格等多種資源,UIWebView在加載資源的時候會不斷調整contentSize以渲染新加載完成的內容。
//contentSize變化時,重新布局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”];
}
//設置footerView的合理位置
- (void)layoutFooterView{
//取消監聽,因為這里會調整contentSize,避免無限遞歸
[self removeObserverForWebViewContentSize];
CGSize contentSize = _webView.scrollView.contentSize;
CGFloat y = CGRectGetMaxY(_webView.webBrowserView.frame);
//設置footerView的位置
_footerView.frame = CGRectMake(0, y, contentSize.width, footerHeight);
[_webView.scrollView addSubview:_footerView];
_webView.scrollView.contentSize = CGSizeMake(contentSize.width, y + footerHeight);
//重新監聽
[self addObserverForWebViewContentSize];
}
//下圖底部紅色區域即footerView
image
滑動隱藏頂部Bar
如果你想在用戶向上滑動時隱藏頂部bar,向下滑動時顯示頂部bar,該怎么辦呢?
_webView.scrollView的delegate是_webView自身,你是不能接管的,所以拿不到scrollView的相關事件。
怎么辦呀???
好吧,答案就是: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];
}
}