问题汇总
1、如何实现JavaScript与Objective-C间传值?
点击Webview中的图片,放大,需要JavaScript和Objective-C传值,获取到具体需要放大哪张图片。
本方案中,不需要引入WebViewJavascriptBridge,而是通过【控制Webview重定向方法,拦截发出的请求】来实现。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // 每个添加点击事件(window.location.href),其中selectIndex为图片标识// webview发起请求拦截 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ // 获取img标识index NSString *url = request.URL.absoluteString; NSRange range = [url rangeOfString:@ "selectIndex=" ]; if (range.location != NSNotFound) { NSInteger begin = range.location + range.length; NSString *index = [url substringFromIndex:begin]; NSLog(@ "img: %@" , index); return NO; } return YES; } |
2、如何实现UIWebView高度自适应?
UIWebView自适应高度的方案有很多,选择一个较为科学的方式,显得尤为重要。
本方案中,通过【KVO监听Webview的contentSize】来实现,需要注意KVO的添加、移除,稍有不慎有Crash风险。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // 添加监听 [self.webView.scrollView addObserver:self forKeyPath:@ "contentSize" options:NSKeyValueObservingOptionNew context:nil]; - (UIWebView *)webView { if (!_webView) { _webView = [[UIWebView alloc] initWithFrame:CGRectMake( 0 , 0 , self.frame.size.width, self.frame.size.height)]; _webView.delegate = self; _webView.scrollView.bounces = NO; _webView.scrollView.showsHorizontalScrollIndicator = NO; _webView.scrollView.scrollEnabled = NO; _webView.scalesPageToFit = YES; } return _webView; } // 修改webview的frame - ( void )observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:( void *)context { if ([keyPath isEqualToString:@ "contentSize" ]) { CGSize resize = [self.webView sizeThatFits:CGSizeZero]; self.webView.frame = CGRectMake( 0 , 0 , CGRectGetWidth(self.frame), resize.height); } } // 移除监听 -( void )dealloc { [self.webView.scrollView removeObserver:self forKeyPath:@ "contentSize" ]; } |
3、如何实现UIWebView显示webp格式图片?
UIWebView、WKWebview本身都不支持webp格式图片,需要额外扩展。
示例:
1 2 3 4 5 6 7 8 | // 导入头文件 # import // 注册 MagicURLProtocol [[MagicWebViewWebPManager shareManager] registerMagicURLProtocolWebView:self.webView]; // 销毁 MagicURLProtocol -( void )dealloc { [[MagicWebViewWebPManager shareManager] unregisterMagicURLProtocolWebView:self.webView]; } |
4、如何实现图文混排 + UIKit组件?
使用UIWebView加载自定义HTML代码的方式,实现图文混排。
点击图片,放大,function()跳转链接,携带selectIndex标识,通过拦截UIWebView的请求来获取selectIndex标识。
通过KVO获取到WebView高度,重新设置webView.frame,collectionView.frame,scrollView.contentSize
本方案中,图文混排+UIKit组件,具体逻辑如下:
5、如何自定义HTML代码?
本方案中,以纯图片为例,处理后的HTML如下:
1 | ...... |
6、如何实现并发执行多个网络请求,统一处理?
本方案中,利用GCD创建队列组,提交多个任务到队列组,多个任务同时执行,监听队列组执行完毕,在主线程刷新UI。
注意: dispatch_group_enter() 、 dispatch_group_leave()将队列组中的任务未执行完毕的任务数目加减1(两个函数要配合使用)
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | - ( void )exampleMoreNetwork{ dispatch_group_t group = dispatch_group_create(); dispatch_queue_t serialQueue = dispatch_queue_create( "magic_gcd_group" , DISPATCH_QUEUE_SERIAL); // 网络请求1 dispatch_group_enter(group); dispatch_group_async(group, serialQueue, ^{ [[MagicNetworkManager shareManager] GET:@ "网络请求1" Parameters:nil Success:^(NSURLResponse *response, id responseObject) { dispatch_group_leave(group); } Failure:^(NSURLResponse *response, id error) { dispatch_group_leave(group); }]; }); // 网络请求2 dispatch_group_enter(group); dispatch_group_async(group, serialQueue, ^{ [[MagicNetworkManager shareManager] GET:@ "网络请求2" Parameters:nil Success:^(NSURLResponse *response, id responseObject) { dispatch_group_leave(group); } Failure:^(NSURLResponse *response, id error) { dispatch_group_leave(group); }]; }); // 所有网络请求结束 dispatch_group_notify(group, serialQueue, ^{ dispatch_async(dispatch_get_global_queue( 0 , 0 ), ^{ dispatch_async(dispatch_get_main_queue(), ^{ // 主线程刷新UI }); }); }); } |
图文混排——核心
目录结构
实现代理方法,放大图片,跳转商品,置顶。
实现针对showjoy.com域名,图片url拼接.webp。
实现UIScrollView作为根容器,自适应内容高度。
实现UIWebView支持webp格式图片。
实现自定义HTML代码,图片居中,window.location.href事件传递selectIndex,UIWebView代理拦截selectIndex。
通过HTML,JavaScript,还可以实现更多功能。。。。。。
ProductLoadMorePicTextView.h
1 2 3 4 5 6 7 8 9 10 11 | # import # import "ProductDetailModel.h" # import "ProductLoadMorePicTextModel.h" @protocol ProductLoadMorePicTextViewDelegate - ( void )productLoadMorePicTextViewZoomImageWithIndex:(NSInteger)index; - ( void )productLoadMorePicTextViewPushProductWithSkuId:(NSString *)skuId; - ( void )productLoadMorePicTextViewGoTop; @end @ interface ProductLoadMorePicTextView : UIView @property (nonatomic, weak) id delegate; - (instancetype)initWithFrame:(CGRect)frame productDetailModel:(ProductDetailModel *)productDetailModel picTextModel:(ProductLoadMorePicTextModel *)picTextModel; - ( void )reload; @end |
ProductLoadMorePicTextView.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | # import "ProductLoadMorePicTextView.h" # import "ProductLoadMorePicTextCollectionViewCell.h" # import "MagicScrollPageRefreshHeader.h" # import static const CGFloat recommendViewHeight = 170.0 ; static const CGFloat recommendViewSpace = 10.0 ; static const CGFloat recommendItemWidth = 105.0 ; static const CGFloat recommendItemSpace = 5.0 ; static const CGFloat recommendTitleHeight = 40.0 ; @ interface ProductLoadMorePicTextView ()@property (nonatomic, strong) UIScrollView *scrollView; @property (nonatomic, strong) UIWebView *webView; @property (nonatomic, strong) UICollectionView *collectionView; @property (nonatomic, strong) UILabel *recommendLabel; @property (nonatomic, strong) NSMutableArray *recommendDataArray; @property (nonatomic, strong) NSMutableArray *picTextDataArray; @end @implementation ProductLoadMorePicTextView - (instancetype)initWithFrame:(CGRect)frame productDetailModel:(ProductDetailModel *)productDetailModel picTextModel:(ProductLoadMorePicTextModel *)picTextModel { self = [ super initWithFrame:frame]; if (self) { self.recommendDataArray = [NSMutableArray arrayWithArray:productDetailModel.recommend]; self.picTextDataArray = [NSMutableArray arrayWithArray:picTextModel.itemPic.packageImages]; [self createSubViewsWithPicTextModel:picTextModel]; } return self; } - ( void )createSubViewsWithPicTextModel:(ProductLoadMorePicTextModel *)picTextModel { [self addSubview:self.scrollView]; [[MagicWebViewWebPManager shareManager] registerMagicURLProtocolWebView:self.webView]; [self.scrollView addSubview:self.webView]; [self.scrollView addSubview:self.recommendLabel]; [self.scrollView addSubview:self.collectionView]; [self.webView.scrollView addObserver:self forKeyPath:@ "contentSize" options:NSKeyValueObservingOptionNew context:nil]; MC_SELF_WEAK(self) MagicScrollPageRefreshHeader *header = [MagicScrollPageRefreshHeader headerWithRefreshingBlock:^{ [weakself.scrollView.mj_header endRefreshing]; [weakself executeProductLoadMorePicTextViewGoTop]; }]; self.scrollView.mj_header = header; } #pragma mark -Lazy - (UIScrollView *)scrollView { if (!_scrollView) { _scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake( 0 , 0 , self.frame.size.width, self.frame.size.height)]; _scrollView.backgroundColor = [UIColor colorWithRed: 0.95 green: 0.95 blue: 0.95 alpha: 1.00 ]; } return _scrollView; } - (UIWebView *)webView { if (!_webView) { _webView = [[UIWebView alloc] initWithFrame:CGRectMake( 0 , 0 , self.frame.size.width, self.frame.size.height)]; _webView.delegate = self; _webView.scrollView.bounces = NO; _webView.scrollView.showsHorizontalScrollIndicator = NO; _webView.scrollView.scrollEnabled = NO; _webView.scalesPageToFit = YES; } return _webView; } - (UILabel *)recommendLabel{ if (!_recommendLabel) { _recommendLabel = [[UILabel alloc] init]; _recommendLabel.text = @ " 更多推荐" ; _recommendLabel.textColor = [UIColor colorWithRed: 0.30 green: 0.30 blue: 0.30 alpha: 1.00 ]; _recommendLabel.font = [UIFont systemFontOfSize: 12 ]; _recommendLabel.backgroundColor = [UIColor whiteColor]; } return _recommendLabel; } - (UICollectionView *)collectionView { if (!_collectionView) { UICollectionViewFlowLayout *flowLayout = [UICollectionViewFlowLayout new ]; flowLayout.sectionInset = UIEdgeInsetsMake( 0 , 0 , 0 , recommendItemSpace); flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; flowLayout.itemSize = CGSizeMake(recommendItemWidth, recommendViewHeight); _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:flowLayout]; _collectionView.backgroundColor = [UIColor whiteColor]; _collectionView.delegate = self; _collectionView.dataSource = self; [_collectionView registerClass:[ProductLoadMorePicTextCollectionViewCell class ] forCellWithReuseIdentifier:@ "cell" ]; } return _collectionView; } - ( void )observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:( void *)context { if ([keyPath isEqualToString:@ "contentSize" ]) { CGSize resize = [self.webView sizeThatFits:CGSizeZero]; self.webView.frame = CGRectMake( 0 , 0 , CGRectGetWidth(self.frame), resize.height); self.recommendLabel.frame = CGRectMake( 0 , CGRectGetMaxY(self.webView.frame) + recommendViewSpace, CGRectGetWidth(self.frame), recommendTitleHeight); self.collectionView.frame = CGRectMake( 0 , CGRectGetMaxY(self.recommendLabel.frame), CGRectGetWidth(self.frame), recommendViewHeight); self.scrollView.contentSize = CGSizeMake(CGRectGetWidth(self.frame), CGRectGetMaxY(self.collectionView.frame) + recommendViewSpace); } } -( void )dealloc { [[MagicWebViewWebPManager shareManager] unregisterMagicURLProtocolWebView:self.webView]; [self.webView.scrollView removeObserver:self forKeyPath:@ "contentSize" ]; self.scrollView = nil; self.webView = nil; self.collectionView = nil; self.recommendDataArray = nil; self.picTextDataArray = nil; } #pragma mark - UIWebViewDelegate - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ return [self handleWebviewEventWithRequest:request]; } - ( void )webViewDidStartLoad:(UIWebView *)webView { } - ( void )webViewDidFinishLoad:(UIWebView *)webView { } - ( void )webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { NSLog(@ "商品详情web错误 %@" , error); } - (BOOL)handleWebviewEventWithRequest:(NSURLRequest *)request { NSString *url = request.URL.absoluteString; NSRange range = [url rangeOfString:@ "selectIndex=" ]; if (range.location != NSNotFound) { NSInteger begin = range.location + range.length; NSString *index = [url substringFromIndex:begin]; [self executeProductLoadMorePicTextViewZoomImageWithIndexString:index]; return NO; } return YES; } #pragma mark - CustomHTML - ( void )loadWebViewCustomHTMLWithImageUrls:(NSArray *)imageUrls { NSMutableString *html = [NSMutableString string]; [html appendString:@ "" ]; [html appendString:@ "" ]; [html appendString:@ "" ]; [html appendString:@ "" ]; [html appendString:[self settingWebViewBodyWithImageUrlArray:imageUrls]]; [html appendString:@ "" ]; [html appendString:@ "" ]; [self.webView loadHTMLString:html baseURL:nil]; } - (NSString *)settingWebViewBodyWithImageUrlArray:(NSArray *)imageUrlArray { NSMutableString *body = [NSMutableString string]; for (NSInteger i = 0 ; i < imageUrlArray.count; i++) { NSString *imgUrl = [NSString stringWithFormat:@ "%@" , [imageUrlArray objectAtIndex:i]]; imgUrl = [self handlerImgUrlString:imgUrl]; NSMutableString *html = [NSMutableString string]; [html appendString:@ "" ]; NSString *onload = [NSString stringWithFormat:@ "this.onclick = function() {window.location.href = 'selectIndex=' + %ld;}" , i]; [html appendFormat:@ "" , onload, imgUrl]; [html appendString:@ "" ]; [body appendString:html]; } return body; } #pragma mark -UICollectionViewDataSource - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ return self.recommendDataArray.count; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ ProductLoadMorePicTextCollectionViewCell *cell = (ProductLoadMorePicTextCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@ "cell" forIndexPath:indexPath]; cell.productRecommendModel = [self.recommendDataArray objectAtIndex:indexPath.row]; return cell; } #pragma mark -UICollectionViewDelegate - ( void )collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{ ProductRecommend *productRecommendModel = [self.recommendDataArray objectAtIndex:indexPath.row]; [self executeProductLoadMorePicTextViewPushProductWithSkuId:productRecommendModel.ID]; } #pragma mark -ProductLoadMoreViewDelegate - ( void )executeProductLoadMorePicTextViewZoomImageWithIndexString:(NSString *)indexString { if ([self.delegate respondsToSelector:@selector(productLoadMorePicTextViewZoomImageWithIndex:)]) { [self.delegate productLoadMorePicTextViewZoomImageWithIndex:[indexString integerValue]]; } } - ( void )executeProductLoadMorePicTextViewPushProductWithSkuId:(NSInteger)skuId { if ([self.delegate respondsToSelector:@selector(productLoadMorePicTextViewPushProductWithSkuId:)]) { [self.delegate productLoadMorePicTextViewPushProductWithSkuId:[NSString stringWithFormat:@ "%ld" , skuId]]; } } - ( void )executeProductLoadMorePicTextViewGoTop { if ([self.delegate respondsToSelector:@selector(productLoadMorePicTextViewGoTop)]) { [self.delegate productLoadMorePicTextViewGoTop]; } } #pragma mark - Reload - ( void )reload{ [self loadWebViewCustomHTMLWithImageUrls:self.picTextDataArray]; [self.collectionView reloadData]; } #pragma mark - IMGURL - (NSString *)handlerImgUrlString:(NSString *)imgUrlString { NSString *result = [NetworkManager httpsSchemeHandler:imgUrlString]; // webp if ([result containsString:@ "showjoy.com" ] && ![result hasSuffix:@ ".webp" ]) { result = [result stringByAppendingString:@ ".webp" ]; } return result; } @end |
图文混排——使用
ProductLoadMoreViewController中,保证两个接口都请求完成后,刷新ProductLoadMorePicTextView。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | # import "ProductLoadMoreViewController.h" # import "MagicNetworkManager.h" # import "ProductLoadMorePicTextView.h" static NSString * const SJProductAPI = @ "https://shopappserver.showjoy.com/api/shop/sku" ; static NSString * const SJProductPicTextAPI = @ "https://shopappserver.showjoy.com/api/shop/item/pictext" ; static NSString * const SJProductSkuId = @ "146931" ; @ interface ProductLoadMoreViewController () @end @implementation ProductLoadMoreViewController{ ProductDetailModel *_productModel; ProductLoadMorePicTextModel *_productPicTextModel; ProductLoadMorePicTextView *_picTextView; } - ( void )viewDidLoad { [ super viewDidLoad]; // Do any additional setup after loading the view. self.view.backgroundColor = [UIColor grayColor]; [self networkRequestData]; } #pragma mark - Network - ( void )networkRequestData { [QuicklyHUD showWindowsProgressHUDText:@ "加载中..." ]; dispatch_group_t group = dispatch_group_create(); dispatch_queue_t serialQueue = dispatch_queue_create( "product_group" , DISPATCH_QUEUE_SERIAL); // 商品信息 dispatch_group_enter(group); dispatch_group_async(group, serialQueue, ^{ [[MagicNetworkManager shareManager] GET:SJProductAPI Parameters:@{@ "skuId" : SJProductSkuId} Success:^(NSURLResponse *response, id responseObject) { [ProductDetailModel mj_setupObjectClassInArray:^NSDictionary *{ return @{@ "shop" : [ProductShop class ], @ "skuList" : [ProductSkuList class ], @ "value" : [ProductValue class ], @ "saleInfo" : [ProductSaleInfo class ], @ "recommend" : [ProductRecommend class ], @ "skuCommission" : [ProductSkuCommission class ], @ "item" : [ProductItem class ], @ "tagSkus" : [ProductTagSkus class ], @ "tagMap" : [ProductTagMap class ], @ "skuEnsures" : [ProductSkuEnsures class ], @ "salesPromotion" : [ProductSalesPromotion class ]}; }]; _productModel = [ProductDetailModel mj_objectWithKeyValues:[responseObject valueForKey:@ "data" ]]; dispatch_group_leave(group); } Failure:^(NSURLResponse *response, id error) { dispatch_group_leave(group); }]; }); // 图文信息 dispatch_group_enter(group); dispatch_group_async(group, serialQueue, ^{ [[MagicNetworkManager shareManager] GET:SJProductPicTextAPI Parameters:@{@ "skuId" : SJProductSkuId} Success:^(NSURLResponse *response, id responseObject) { [ProductLoadMorePicTextModel mj_setupObjectClassInArray:^NSDictionary *{ return @{@ "item" : [PicTextItem class ], @ "itemPic" : [PicTextItemPic class ], @ "spu" : [PicTextSpu class ]}; }]; _productPicTextModel = [ProductLoadMorePicTextModel mj_objectWithKeyValues:[responseObject valueForKey:@ "data" ]]; dispatch_group_leave(group); } Failure:^(NSURLResponse *response, id error) { dispatch_group_leave(group); }]; }); // 主线程刷新UI dispatch_group_notify(group, serialQueue, ^{ dispatch_async(dispatch_get_global_queue( 0 , 0 ), ^{ dispatch_async(dispatch_get_main_queue(), ^{ [QuicklyHUD hiddenMBProgressHUDForView:MC_APP_WINDOW]; [self reloadPicTextView]; }); }); }); } #pragma mark - Reload - ( void )reloadPicTextView { if (_picTextView) { [_picTextView removeFromSuperview]; _picTextView.delegate = nil; _picTextView = nil; } CGFloat border = 20 .0f; _picTextView = [[ProductLoadMorePicTextView alloc] initWithFrame:CGRectMake(border, border, MC_SCREEN_W - 2 * border, MC_SCREEN_H - MC_NAVIGATION_BAR_H - MC_STATUS_BAR_H - 2 * border) productDetailModel:_productModel picTextModel:_productPicTextModel]; _picTextView.delegate = self; [self.view addSubview:_picTextView]; [_picTextView reload]; } #pragma mark - ProductLoadMorePicTextViewDelegate - ( void )productLoadMorePicTextViewGoTop { [QuicklyHUD showWindowsOnlyTextHUDText:@ "Go Top" ]; } - ( void )productLoadMorePicTextViewZoomImageWithIndex:(NSInteger)index { [QuicklyHUD showWindowsOnlyTextHUDText:[NSString stringWithFormat:@ "img: %ld" , index]]; } - ( void )productLoadMorePicTextViewPushProductWithSkuId:(NSString *)skuId { [QuicklyHUD showWindowsOnlyTextHUDText:[NSString stringWithFormat:@ "skuId: %@" , skuId]]; } @end
|