Implementation approach
- Splice text and images into HTML code.
- Add click events using JavaScript.
- Use MagicWebViewWebP to provide UIWebView support for loading WebP images.
- Load the HTML code using UIWebView.
- Use the UIWebView proxy method to intercept the request from the page and get the selectIndex.
Implementation effect
component | describe | instructions |
---|---|---|
UIScrollView | The root container | Height adaptive (KVO handles UIWebView + UICollectionView height) |
UIWebView | Mixed display of text and text | Loading HTML code |
UICollectionView | More recommended displays | There is no |
Implementation effect
The problem summary
1. How to transfer values between JavaScript and Objective-C?
Click on the image in the Webview to enlarge it, and you need JavaScript and Objective-C to upload the value to get the specific image to enlarge.
In this scheme, it is not necessary to introduce Webview javascriptBridge, but to achieve it by [controlling Webview redirection method and intercepting the request sent].
Example:
// Each add click event (window.location.href), Including selectIndex picture id / / webview initiate request intercept - (BOOL) webview: (UIWebView *) webview shouldStartLoadWithRequest: (NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{// get 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; }Copy the code
2, how to achieve UIWebView highly adaptive?
There are many schemes for UIWebView to adapt to the height, and it is particularly important to choose a more scientific way.
In this scheme, it is realized by [KVO listening to Webview contentSize], so it is necessary to pay attention to the addition and removal of KVO, as there is a risk of Crash if it is careless.
Example:
/ / add to monitor [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; } // modify the 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); [self.webView.scrollView removeObserver:self forKeyPath:@"contentSize"]; }Copy the code
3. How to implement UIWebView to display webP images?
UIWebView and WKWebview themselves do not support WebP format images, requiring additional extensions.
Can download MagicWebViewWebP, direct access to my lot, will directly import MagicWebViewWebP. Framework 】 【 engineering.
UIWebView and WKWebView support WebP image display
Example:
MagicURLProtocol [[MagicWebViewWebPManager shareManager] registerMagicURLProtocolWebView:self.webView]; MagicURLProtocol -(void)dealloc {[[MagicWebViewWebPManager shareManager] unregisterMagicURLProtocolWebView:self.webView]; }Copy the code
4, how to achieve text and text mix + UIKit components?
Use UIWebView to load custom HTML code, text and text mix.
Click on the image to zoom in, and the function() jump link carries the selectIndex flag, which is retrieved by intercepting UIWebView requests.
Set webView.frame, CollectionView. frame, and scrollView.contentSize
In this scheme, text and text mix +UIKit components, the specific logic is as follows:
5. How to customize HTML code?
In this scheme, pure images are taken as an example, and the processed HTML is as follows:
.Copy the code
6. How to realize concurrent execution of multiple network requests and unified processing?
In this scheme, GCD is used to create a queue group, submit multiple tasks to the queue group, execute multiple tasks at the same time, monitor the completion of the queue group, and refresh the UI in the main thread.
Note: dispatch_group_enter() and dispatch_group_leave() add or subtract one from the number of uncompleted tasks in the queue group.
Reference: Play communist CD
Example:
- (void)exampleMoreNetwork{ dispatch_group_t group = dispatch_group_create(); dispatch_queue_t serialQueue = dispatch_queue_create("magic_gcd_group", DISPATCH_QUEUE_SERIAL); // Network request 1 dispatch_group_enter(group); dispatch_group_async(group, serialQueue, ^{[[MagicNetworkManager shareManager] GET:@" networkManager shareManager "Parameters:nil Success:^(NSURLResponse *response, id responseObject) { dispatch_group_leave(group); } Failure:^(NSURLResponse *response, id error) { dispatch_group_leave(group); }]; }); // Network request 2 dispatch_group_enter(group); dispatch_group_async(group, serialQueue, ^{[[MagicNetworkManager shareManager] GET:@" networkManager shareManager "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(), ^{// main thread refresh UI}); }); }); }Copy the code
Text and text mix – core
The directory structure
Realization of agent method, enlarge the picture, jump commodity, top.
For showjoy.com domain name, image URL splicing. Webp.
Implement UIScrollView as the root container, adaptive content height.
Implement UIWebView to support WebP format images.
Implement custom HTML code, image center, window.location.href event passes selectIndex, UIWebView proxy intercepts selectIndex.
You can do much more with HTML, JavaScript…
ProductLoadMorePicTextView.h
#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;
@endCopy the code
ProductLoadMorePicTextView.m
#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;
}
@endCopy the code
Text and text mix – use
ProductLoadMoreViewController, guarantee both interface request is completed, the refresh ProductLoadMorePicTextView.
#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: @ "loading..." ; dispatch_group_t group = dispatch_group_create(); dispatch_queue_t serialQueue = dispatch_queue_create("product_group", DISPATCH_QUEUE_SERIAL); // Commodity information 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); }]; }); 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]]; } @endCopy the code
Demo
– ProductLoadMoreViewController MagicCubeKit – laboratory
Author: LuisX
Link: https://www.jianshu.com/p/7c0c5b9158e8
LuisX issue https://github.com/Luis-X/Blog/issues