重构webView页面间跳转逻辑
在处理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–
若无特别说明,本站文章均为原创,转载请保留链接,谢谢