重构webView页面间跳转逻辑

Author Avatar
XibHe 8月 07, 2017
  • 在其它设备中阅读本文章

在处理webView页面内的跳转逻辑时,需要在当前webView中点击某个按钮push到另一个webView页,在这个新的webView页中仍然可以操作页面上的点击事件,跳转到其他webView页面。于是,这些webView页面可以相互跳转,不断跳转新的或者之前的webView页面。

业务需求

点击商品进入到“商品详情”页(GoodInforH5WebViewController),点击店铺进入“店铺详情”页(ShopInfoH5WebViewController),其中(“商品详情”页和”店铺详情”页均为webView)。其中,店铺详情页和商品详情页可以相互跳转。

当前逻辑

在继承于UINavigationController的自定义导航控制器MyNavigationController里做判断,重写方法:

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    if (self.viewControllers.count > 0)
    {
        if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
        {
            self.interactivePopGestureRecognizer.delegate = nil;
        }
        viewController.hidesBottomBarWhenPushed = YES;
        if ([viewController isKindOfClass:[GoodInforH5WebViewController class]] || [viewController isKindOfClass:[ShopInfoH5WebViewController class]]) {
            viewController.navigationItem.leftBarButtonItem = [UIBarButtonItem itemWithTarget:self action:@selector(backRootViewController) image:@"navigation_back" highlightImage:@"navigation_back"];
        }
    }     
   [super pushViewController:viewController animated:YES];
}

重写GoodInforH5WebViewController和ShopInfoH5WebViewController的返回事件。即,backRootViewController。在backRootViewController方法里做相应页面的跳转操作。

在GoodInforH5WebViewController页面的viewWillAppear方法里进行页面跳转逻辑的判断。如下:

前方高能预警!!!
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self.navigationController setNavigationBarHidden:NO animated:NO];

    //[_webView reload];
    CLog(@"self.navigationController.viewControllers count = %ld",[self.navigationController.viewControllers count]);
    CLog(@"self.navigationController.viewControllers = %@",self.navigationController.viewControllers);
    // 若是经首页;分类列表页面;订单中心;商业店铺列表页;我的收藏,且viewControllers<5,则设置pop回上一页的标识;商家促销,且viewControllers<5,则设置pop回上一页的标识
    if ([_fromWebUrl isEqualToString:@"home_bussinessUrl"] || ([_fromWebUrl isEqualToString:@"assortment_searchByCate"] && [self.navigationController.viewControllers count] < 5) ||
        [_fromWebUrl isEqualToString:@"sellOrderCenter_buyOrders"] || 
        ([_fromWebUrl isEqualToString:@"My_collectionListUrl"] && [self.navigationController.viewControllers count] < 5) ||
         [_fromWebUrl isEqualToString:@"businessShopSearchList_goodInfo"] ||  
         ([_fromWebUrl isEqualToString:@"ActivityViewController"] && 
         [self.navigationController.viewControllers count] < 5)  || ([_fromWebUrl isEqualToString:@"BusinessPromoteViewController"] && [self.navigationController.viewControllers count] < 5)) {
        [[NSUserDefaults standardUserDefaults] setObject:@"1" forKey:KPopHomeIndentify];
        [[NSUserDefaults standardUserDefaults] synchronize];
    } else if (([_fromWebUrl isEqualToString:@"GoodsListViewController"] && [self.navigationController.viewControllers count] < 5) || [_fromWebUrl isEqualToString:@"search_searchByKey"] || [_fromWebUrl isEqualToString:@"business_shopSearch"] || ([_fromWebUrl isEqualToString:@"home_searchVC"] && [self.navigationController.viewControllers count] < 6) || [_fromWebUrl isEqualToString:@"businessShopSearchList_shopInfo"] || ([_fromWebUrl isEqualToString:@"home_productDetailUrl"] && [self.navigationController.viewControllers count] >= 4) || ([_fromWebUrl isEqualToString:@"ShopCarListViewController"] && [self.navigationController.viewControllers count] >= 4)){
        // 从GoodsListViewController跳转至原生商品详情页面;从顶部搜索等页面跳转至原生商品详情页面; 从商业店铺搜索结果列表页跳转,点击返回至self.viewControllers[2]的页面;若是从搜索首页,分类的搜索结果页跳转而来,返回搜索商品列表页;若是从商业店铺列表页跳转而来,则返回到店铺详情的H5页面;从首页商品-->商品详情-->店铺详情-->商品详情,并且viewControllers>=4,则返回店铺详情页;从原生购物车跳转而来,且viewControllers>=4,则返回店铺详情页;
        [[NSUserDefaults standardUserDefaults] setObject:@"2" forKey:KPopHomeIndentify];
        [[NSUserDefaults standardUserDefaults] synchronize];
    } else if (([_fromWebUrl isEqualToString:@"shopInfor_searchVC"] && [self.navigationController.viewControllers count] < 7) || ([_fromWebUrl isEqualToString:@"assortment_searchByCate"] && [self.navigationController.viewControllers count] >= 5) || ([_fromWebUrl isEqualToString:@"My_collectionListUrl"] && [self.navigationController.viewControllers count] >= 5) || ([_fromWebUrl isEqualToString:@"ActivityViewController"] && [self.navigationController.viewControllers count] >= 5) || ([_fromWebUrl isEqualToString:@"GoodsListViewController"] && [self.navigationController.viewControllers count] == 5)) {
        // 若是从商业店铺详情搜索跳转而来;若是从分类列表页跳转而来;若是从我的收藏跳转而来,且viewControllers>=5,则返回店铺详情页面。
        [[NSUserDefaults standardUserDefaults] setObject:@"3" forKey:KPopHomeIndentify];
        [[NSUserDefaults standardUserDefaults] synchronize];
    } else if (([_fromWebUrl isEqualToString:@"home_searchVC"] && [self.navigationController.viewControllers count] >= 6) || ([_fromWebUrl isEqualToString:@"GoodsListViewController"] && [self.navigationController.viewControllers count] >= 6)) {
        // 从搜索商品列表-->商品详情-->店铺详情-->商品详情,且viewControllers>=6,则返回店铺详情页;从订单详情页-->商品详情-->店铺详情-->商品详情,且viewControllers>=6,则返回店铺详情页;
        [[NSUserDefaults standardUserDefaults] setObject:@"4" forKey:KPopHomeIndentify];
        [[NSUserDefaults standardUserDefaults] synchronize];
    } else if ([_fromWebUrl isEqualToString:@"shopInfor_searchVC"] && [self.navigationController.viewControllers count] >= 7) {
        // 从搜索商品列表-->商品详情-->店铺详情-->商品详情,且viewControllers>=7,则返回店铺详情页。
        [[NSUserDefaults standardUserDefaults] setObject:@"5" forKey:KPopHomeIndentify];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
    else {
        [[NSUserDefaults standardUserDefaults] setObject:@"0" forKey:KPopHomeIndentify];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
}

这里是通过设置一个名为KPopHomeIndentify的NSUserDefaults对象用于存储navigationController中,当前页面的层级。通过设置一个名为_fromWebUrl的字符串,用于判断是哪个页面跳转而来的。二者结合起来,设置经过GoodInforH5WebViewController页面的返回逻辑。

当触发商品详情页的跳转链接时,需要在webView的shouldStartLoadWithRequest代理方法里做判断,当request的绝对地址中包含店铺详情的路径时,则push到店铺详情页。如下:

#pragma mark - UIWebViewDelegate
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    _absoluteUrl = [request.URL absoluteString];

    // push到店铺详情页面
    if ([_absoluteUrl rangeOfString:@"appLink/showBusyInfo?"].location != NSNotFound) {

        ShopInfoH5WebViewController *shopInforH5WebVC = [[ShopInfoH5WebViewController alloc] init];
        shopInforH5WebVC.webUrl = _absoluteUrl;
        shopInforH5WebVC.fromWebUrl = _fromWebUrl;
        [self.navigationController pushViewController:shopInforH5WebVC animated:YES];

        return NO;
    } else {
        return YES;
    }
}

再回到自定义导航控制器MyNavigationController重写的返回事件中:

- (void)backRootViewController
{
    CLog(@"self.viewControllers = %@",self.viewControllers);
    NSString *popIndentify = [[NSUserDefaults standardUserDefaults] objectForKey:KPopHomeIndentify];
    UIViewController *viewCtl;
    if ([popIndentify isEqualToString:@"0"]) {
        viewCtl = self.viewControllers[0];
    } else if ([popIndentify isEqualToString:@"1"]) {
        viewCtl = self.viewControllers[1];
    } else if ([popIndentify isEqualToString:@"2"]) {
        viewCtl = self.viewControllers[2];
    } else if ([popIndentify isEqualToString:@"3"]) {
        viewCtl = self.viewControllers[3];
    } else if ([popIndentify isEqualToString:@"4"]) {
        viewCtl = self.viewControllers[4];
    } else if ([popIndentify isEqualToString:@"5"]) {
        viewCtl = self.viewControllers[5];
    }
    [self popToViewController:viewCtl animated:YES];
}

同样是结合GoodInforH5WebViewController中设置好的KPopHomeIndentify进行判断。根据KPopHomeIndentify存储的导航控制器中的页面层级,在导航控制器中通过这些层级获取到对应的页面viewController,最后popToViewController到这些页面中。

重构原因

看了上面那一片高能预警的代码逻辑,重构原因就不言而喻了。

*逻辑判断复杂,每次新增与商品详情,店铺详情相关页面时,都需要新增一系列对应页面层级的判断逻辑;

*给调试造成困难,与商品详情,店铺详情关联的页面很多,需要进行多场景的关联测试;

*最重要的一点:每次从商品详情页跳转到店铺详情都需要重新重新alloc一个新的ShopInfoH5WebViewController对象,耗费了很多资源。

重构逻辑

通过设置NSUserDefaults存储页面层级方式进行页面跳转的逻辑,不易操纵,并且非常的不合理,也降低了代码的可读性。设想一下,这里之所以明确不同跳转页面在navigationController中的层级关系,是为了返回操作能回退到指定页面。而导致该冗余代码的罪魁祸首是:每次触发商品详情页或店铺详情页的跳转链接时,都会重新alloc一个新的对象。在进行多次跳转操作后,navigationController中控制的层级就会一直增加,若想在pop操作时跳转到指定页面,就必须明确所要跳转页面在导航控制器中的层级。

那接下来的操作就很明确了,首先移除GoodInforH5WebViewController中viewWillAppear里的一堆判断代码;然后,在webView的代理方法shouldStartLoadWithRequest中增加跳转判断,如下:

#pragma mark - UIWebViewDelegate
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    _absoluteUrl = [request.URL absoluteString];
    CLog(@"_absoluteUrl = %@",_absoluteUrl);
        // push到商业店铺详情页面
    if ([_absoluteUrl rangeOfString:@"appLink/showBusyInfo?"].location != NSNotFound) {
        UIViewController *shopInfoH5WebViewController;
        for (shopInfoH5WebViewController in self.navigationController.viewControllers) {
            if ([shopInfoH5WebViewController isKindOfClass:[ShopInfoH5WebViewController class]]) {
                //                addExtractBankCardVCIndex = [self.navigationController.viewControllers indexOfObject:addExtractBankCardVC];
                [self.navigationController popToViewController:shopInfoH5WebViewController animated:YES];
                break;
            } else if ([shopInfoH5WebViewController isKindOfClass:[GoodInforH5WebViewController class]]) {
                ShopInfoH5WebViewController *shopInforH5WebVC = [[ShopInfoH5WebViewController alloc] init];
                shopInforH5WebVC.webUrl = _absoluteUrl;
                shopInforH5WebVC.fromWebUrl = _fromWebUrl;
                [self.navigationController pushViewController: shopInforH5WebVC animated:YES];
                break;
            }
       }       
       return NO;  
   } else {
       return YES;
   }    
}

这里遍历navigationController.viewControllers中所有视图控制器。如果存在店铺详情(ShopInfoH5WebViewController)对象类,则直接popToViewController到该控制器中;如果不存在,则alloc一个新的店铺详情(ShopInfoH5WebViewController),并pushViewController到该页面。

最后仍然需要在自定义导航控制器MyNavigationController重写的返回事件(backRootViewController)中做跳转的逻辑判断。如下:

- (void)backRootViewController
{
    CLog(@"self.viewControllers = %@",self.viewControllers);
    // 当前视图控制器
    UIViewController *currentVC = [self.viewControllers lastObject];
    // 视图控制器的层级
    NSInteger currentVCIndex = [self.viewControllers indexOfObject:currentVC];
    // 上一个页面的视图控制器
    UIViewController *previousVC = self.viewControllers[currentVCIndex - 1];
    if ([currentVC isKindOfClass:[GoodInforH5WebViewController class]]) {
        if ([previousVC isKindOfClass:[ShopInfoH5WebViewController class]]) {
            [self popToViewController:previousVC animated:YES];
        } else {
            [self popViewControllerAnimated:YES];
        }
    } else if ([currentVC isKindOfClass:[ShopInfoH5WebViewController class]]) {
    // 其他会跳转到店铺页面视图的跳转逻辑
        UIViewController *previoussLastVC;
        if ((currentVCIndex - 2) >= 0) {
           previoussLastVC = self.viewControllers[currentVCIndex - 2];
        } else {
            previoussLastVC = [self.viewControllers firstObject];
        }
        if ([previousVC isKindOfClass:[GoodInforH5WebViewController class]] ) {
            // 我的积分跳转
            if ([previoussLastVC isKindOfClass:[OrderDetailsViewController class]]) {
                [self popToViewController:previoussLastVC animated:YES];
            }
            // 我的收藏
            else if ([previoussLastVC isKindOfClass:[MyCollectionViewController class]]) {
                [self popToViewController:previoussLastVC animated:YES];
            }
            // 分类
            else if ([previoussLastVC isKindOfClass:[LonchH5WebController class]] && [self.viewControllers[0] isKindOfClass:[AssortmentViewController class]]) {
                [self popToViewController:previoussLastVC animated:YES];
            }
            // 首页,分类搜索结果页
            else if ([previoussLastVC isKindOfClass:[SearchResultViewController class]]) {
                [self popToViewController:previoussLastVC animated:YES];
            }
            else {
                [self popToRootViewControllerAnimated:YES];
            }
        } else {
            [self popViewControllerAnimated:YES];
        }
     }      
}

这里通过获取当前页面层级,以及当前页面上一个页面的层级,通过这些层级做相应的跳转操作:

如果当前页面是商品详情页(GoodInforH5WebViewController),再进一步判断它的上个页面是否为店铺详情页(ShopInfoH5WebViewController),若是则popToViewController到上个页面,否则直接popViewControllerAnimated;

如果当前页面是店铺详情页(ShopInfoH5WebViewController),则需要判断当前视图层级是否越界(当前页面层级currentVCIndex不会超过3级,可能会遇到导航控制器中只有1个视图控制器的情况),这里与商品详情页不同的是:增加了其它会跳转到店铺页面视图的跳转逻辑。需要对这些页面(积分、我的收藏、分类、搜索结果页)的跳转进行判断。

总结

事不过三,过则重构!

–EOF–

若无特别说明,本站文章均为原创,转载请保留链接,谢谢