原生的 UIWebView 沒有以下幾個(gè)功能
- 右滑返回上個(gè)網(wǎng)頁
- 導(dǎo)航欄左側(cè)的關(guān)閉按鈕
- 網(wǎng)頁加載過程中的進(jìn)度條
- 顯示網(wǎng)頁的域名
在 Github 上找到一個(gè)項(xiàng)目 RxWebViewController 實(shí)現(xiàn)了上面前三個(gè)功能篡诽。根據(jù)需要進(jìn)行了修改:刪除 NJKWebViewProgress慎恒,Reveal,添加域名顯示榜揖,修復(fù)返回某些頁面閃現(xiàn)當(dāng)前頁的問題渠缕。
如何實(shí)現(xiàn)右滑返回上個(gè)網(wǎng)頁
右滑返回上個(gè)網(wǎng)頁鸽素,最簡(jiǎn)單的做法添加手勢(shì)處理[self.webView goBack]
,但這樣沒有滑動(dòng)的慢動(dòng)作效果亦鳞,如何在慢速滑動(dòng)時(shí)能同時(shí)看到當(dāng)前網(wǎng)頁和上個(gè)網(wǎng)頁馍忽?首先需要維護(hù)一個(gè)數(shù)組,保存每個(gè)網(wǎng)頁的截屏燕差。每次在shouldStartLoadWithRequest
調(diào)用snapshotViewAfterScreenUpdates:
截取當(dāng)前網(wǎng)頁屏幕遭笋,壓入數(shù)組中,并在需要的時(shí)候彈出徒探。
#pragma mark - logic of push and pop snap shot views
-(void)pushCurrentSnapshotViewWithRequest:(NSURLRequest*)request{
NSURLRequest* lastRequest = (NSURLRequest*)[[self.snapShotsArray lastObject] objectForKey:@"request"];
UIView* currentSnapShotView = [self.webView snapshotViewAfterScreenUpdates:YES];
[self.snapShotsArray addObject:
@{
@"request":request,
@"snapShotView":currentSnapShotView
}
];
}
我們知道把大象放進(jìn)冰箱需要三個(gè)步驟:打開冰箱瓦呼,放大象進(jìn)去测暗,關(guān)上冰箱磨澡。實(shí)現(xiàn)右滑的慢動(dòng)作,要多一步:
- 截取當(dāng)前網(wǎng)頁蹋辅,同時(shí)取出上一張網(wǎng)頁,將這兩個(gè)
view
添加到self.view
中 - 根據(jù)滑動(dòng)的位置變化侦另,改變兩個(gè)
view
的位置 - 滑動(dòng)結(jié)束,判斷是否返回上一頁褒傅。滑動(dòng)位置超過屏幕的一半則返回殿托,否則取消返回霹菊。
- 完成取消或者返回的動(dòng)畫旋廷,最后刪除兩個(gè)
view
。
#pragma mark - events handler
-(void)swipePanGestureHandler:(UIPanGestureRecognizer*)panGesture{
CGPoint translation = [panGesture translationInView:self.webView];
CGPoint location = [panGesture locationInView:self.webView];
// NSLog(@"pan x %f,pan y %f",translation.x,translation.y);
if (panGesture.state == UIGestureRecognizerStateBegan) {
if (location.x <= 50 && translation.x >= 0) { //開始動(dòng)畫
[self startPopSnapshotView];
}
}else if (panGesture.state == UIGestureRecognizerStateCancelled || panGesture.state == UIGestureRecognizerStateEnded){
[self endPopSnapShotView];
}else if (panGesture.state == UIGestureRecognizerStateChanged){
[self popSnapShotViewWithPanGestureDistance:translation.x];
}
}
刪除 NJKWebViewProgress
使用 NJKWebViewProgress 的問題是每次打開網(wǎng)頁時(shí)常橙牡猓卡在0.1處,會(huì)給用戶產(chǎn)生一種一打開就卡住的感覺馒吴,很不好的體驗(yàn)。查看源碼饮戳,它的進(jìn)度條其實(shí)就三個(gè)值0.1,0.5扯罐,0.9负拟。
const float NJKInitialProgressValue = 0.1f;
const float NJKInteractiveProgressValue = 0.5f;
const float NJKFinalProgressValue = 0.9f;
- (void)incrementProgress
{
float progress = self.progress;
float maxProgress = _interactive ? NJKFinalProgressValue : NJKInteractiveProgressValue;
float remainPercent = (float)_loadingCount / (float)_maxLoadCount;
float increment = (maxProgress - progress) * remainPercent;
progress += increment;
progress = fmin(progress, maxProgress);
[self setProgress:progress];
}
在測(cè)試過程中,而大部分時(shí)候會(huì)停留在0.1處篮赢。這個(gè)在網(wǎng)不好的時(shí)候體驗(yàn)最差齿椅,等半天還是在最初的0.1處,不動(dòng)了启泣。即使Wi-Fi環(huán)境下涣脚,在0.1處會(huì)讓用戶有種卡住的感覺,只是卡住時(shí)間相對(duì)較短寥茫。為了在打開網(wǎng)頁時(shí)遣蚀,就給用戶一種快速加載的感覺,改成計(jì)時(shí)器的實(shí)現(xiàn),勻速增加進(jìn)度芭梯,然后停在接近滿格的地方险耀,直至網(wǎng)頁加載完畢。這種實(shí)現(xiàn)在網(wǎng)絡(luò)情況好的時(shí)候體驗(yàn)非常好玖喘,照顧到了大部分用戶甩牺。因此,在webViewDidStartLoad
添加一個(gè)計(jì)時(shí)器:
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(timerCallback) userInfo:nil repeats:YES];
timer 的回調(diào):
- (void)timerCallback {
if (!self.loading) {
if (self.progressView.progress >= 1) {
self.progressView.hidden = true;
[self.timer invalidate];
}
else {
self.progressView.progress += 0.5;
}
}
else {
self.progressView.progress += 0.05;
if (self.progressView.progress >= 0.9) {
self.progressView.progress = 0.9;
}
}
}
微信的進(jìn)度條并不是勻速增加的累奈,是滿滿減速增加的贬派,如果網(wǎng)頁打開慢最終會(huì)停在大約0.95處。為了有更好的體驗(yàn)澎媒,可以在超過某個(gè)值時(shí)讓增加的進(jìn)度變小搞乏,比如將0.05變?yōu)?.02。
部分網(wǎng)頁滑動(dòng)返回閃現(xiàn)當(dāng)前頁的問題
在webViewDidFinishLoad
里刪除前一頁的截屏戒努,而不是滑動(dòng)結(jié)束即刪除请敦。
[self.prevSnapShotView removeFromSuperview];
加關(guān)閉按鈕
返回鍵使用原生
在viewDidLoad
中加
self.navigationItem.leftItemsSupplementBackButton = YES;
每次打開新的網(wǎng)頁鏈接,更新按鈕:
-(void)updateNavigationItems{
if (self.webView.canGoBack) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
[self.navigationItem setLeftBarButtonItems:@[self.closeButtonItem] animated:NO];
}else{
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
[self.navigationItem setLeftBarButtonItems:nil];
}
}
自定義返回鍵
如果使用了 UINavigationController储玫,那么系統(tǒng)自帶的附加了一個(gè)從屏幕左邊緣開始滑動(dòng)可以實(shí)現(xiàn)pop的手勢(shì)侍筛。但是,如果自定義了 navigationItem.leftBarButtonItem撒穷,那么這個(gè)手勢(shì)就會(huì)失效勾笆。所以就沒辦法右滑返回上級(jí)視圖。解決方法
在主 ViewController viewDidLoad
加
self.navigationController.interactivePopGestureRecognizer.delegate = self;
這樣的話桥滨,在主 ViewController 這個(gè)手勢(shì)也可以響應(yīng),會(huì)導(dǎo)致整個(gè)程序頁面不響應(yīng)弛车。實(shí)現(xiàn) UIGestureRecognizerDelegate 方法:
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if (self.navigationController.viewControllers.count == 1) { //關(guān)閉主界面的右滑返回
return NO;
} else {
return YES;
}
}
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer == self.interactivePopGestureRecognizer) {
return (self.currentShowVC == self.topViewController);
}
return YES;
}
UIWebView updateNavigationItems
修改為
- (void)updateNavigationItems {
if ([self.webView canGoBack]) {
if (!self.swipePanGesture.view) {
[self.webView addGestureRecognizer:self.swipePanGesture];
}
if (self.navigationItem.leftBarButtonItems == nil || [self.navigationItem.leftBarButtonItems count] < 2) {
[self.navigationItem setLeftBarButtonItems:@[self.leftButton, self.closeButton] ];
}
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
else {
self.navigationItem.leftBarButtonItem = self.leftButton;
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
[self.webView removeGestureRecognizer:self.swipePanGesture];
}
}
修改后的代碼在這里齐媒。改動(dòng)的代碼沒有實(shí)現(xiàn)自定義返回鍵,需要的同學(xué)可根據(jù)如上的代碼進(jìn)行修改纷跛。
2016.7.15 修復(fù) timer 引起的內(nèi)存泄漏問題喻括。
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(timerCallback) userInfo:nil repeats:YES];
蘋果的文檔:
The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.
故 timer 對(duì) self 有一個(gè)強(qiáng)引用贫奠,如果在退出 WebViewController 不調(diào)用 [self.timer invalidate]
,由于循環(huán)引用唤崭,將導(dǎo)致內(nèi)測(cè)泄漏。
在以下兩種情況會(huì)產(chǎn)生內(nèi)存泄漏:
- 當(dāng)鏈接做多次跳轉(zhuǎn)腕侄,
webViewDidStartLoad
被調(diào)用一次以上,多次初始化 timer冕杠。而之前的 timer 沒有被 invalidate,從而對(duì) self 保留了強(qiáng)引用兢交。 - 頁面沒加載完全時(shí)退出當(dāng)前 ViewController笼痹,
[self.timer invalidate]
未被調(diào)用時(shí)。
兩處代碼需要修改:
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
self.webView.delegate = nil;
if (self.timer) {
[self.timer invalidate];
}
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
self.progressView.progress = 0;
self.progressView.hidden = false;
self.loading = YES;
if (!self.timer) { // 避免創(chuàng)建多個(gè) timer
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(timerCallback) userInfo:nil repeats:YES];
}
}