iOS 實(shí)現(xiàn)類似微信的 WebView 導(dǎo)航效果

原生的 UIWebView 沒有以下幾個(gè)功能

  1. 右滑返回上個(gè)網(wǎng)頁
  2. 導(dǎo)航欄左側(cè)的關(guān)閉按鈕
  3. 網(wǎng)頁加載過程中的進(jìn)度條
  4. 顯示網(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)作,要多一步:

  1. 截取當(dāng)前網(wǎng)頁蹋辅,同時(shí)取出上一張網(wǎng)頁,將這兩個(gè) view 添加到 self.view
  2. 根據(jù)滑動(dòng)的位置變化侦另,改變兩個(gè)view的位置
  3. 滑動(dòng)結(jié)束,判斷是否返回上一頁褒傅。滑動(dòng)位置超過屏幕的一半則返回殿托,否則取消返回霹菊。
  4. 完成取消或者返回的動(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)存泄漏:

  1. 當(dāng)鏈接做多次跳轉(zhuǎn)腕侄,webViewDidStartLoad被調(diào)用一次以上,多次初始化 timer冕杠。而之前的 timer 沒有被 invalidate,從而對(duì) self 保留了強(qiáng)引用兢交。
  2. 頁面沒加載完全時(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];
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市纺座,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌少欺,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赞别,死亡現(xiàn)場(chǎng)離奇詭異仿滔,居然都是意外死亡犹芹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門飒焦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屿笼,“玉大人,你說我怎么就攤上這事休雌「味希” “怎么了例朱?”我有些...
    開封第一講書人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵洒嗤,是天一觀的道長(zhǎng)魁亦。 經(jīng)常有香客問我,道長(zhǎng)间唉,這世上最難降的妖魔是什么利术? 我笑而不...
    開封第一講書人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮被冒,結(jié)果婚禮上轮蜕,老公的妹妹穿的比我還像新娘。我一直安慰自己跃洛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開白布葱蝗。 她就那樣靜靜地躺著垒玲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叮贩,一...
    開封第一講書人閱讀 51,258評(píng)論 1 300
  • 那天益老,我揣著相機(jī)與錄音,去河邊找鬼捺萌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛酷誓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播棒拂,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼玫氢,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了攻旦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤牢屋,失蹤者是張志新(化名)和其女友劉穎伟阔,沒想到半個(gè)月后掰伸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡合搅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年歧蕉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赌髓。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡催跪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出荣倾,到底是詐尸還是另有隱情,我是刑警寧澤舌仍,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站灌曙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏平匈。R本人自食惡果不足惜藏古,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望隙姿。 院中可真熱鬧厂捞,春花似錦、人聲如沸靡馁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胧弛。三九已至,卻和暖如春结缚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尤勋。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工茵宪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓兽狭,卻偏偏與公主長(zhǎng)得像鹿蜀,于是被迫代替她去往敵國(guó)和親服球。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,094評(píng)論 25 707
  • 有序列表:使用數(shù)字編號(hào)。 表示 無序列表:使用項(xiàng)目符號(hào)粉渠。 表示 定義列表:用來定義專業(yè)術(shù)語。 元素用來包含被定義的...
    應(yīng)該實(shí)現(xiàn)閱讀 617評(píng)論 0 51
  • 今晚突然對(duì)寫手圈產(chǎn)生了一絲興趣雕沉。 讀了一篇文章,講的是怎樣高效而且清晰的表達(dá)你所要說的話坡椒。說話之前要自己思考尤溜,帶上...
    愛人別回頭閱讀 110評(píng)論 0 0
  • 晨起冬雨霖霖落,臘日客行在遠(yuǎn)方丈攒。 寸寸光陰本無異,時(shí)令節(jié)慶皆尋常肥印。 須臾別緒緣何起?風(fēng)云際會(huì)自有章深碱。 吾當(dāng)釋懷歡顏...
    自由和安閱讀 288評(píng)論 1 1
  • 從前的冷梓汐對(duì)于他藏畅,不過是靦腆,害羞幾字就一概而過的绞蹦。可當(dāng)那天晚上由于冷梓汐的好奇幽七。硬是把那個(gè)少年郎不愿說...
    桉m閱讀 125評(píng)論 0 0