iOS-UIScrollView、UIWebView、UICollectionView实现商品详情页图文混排

 

实现思路

  1. 将文本和图片拼接为HTML代码。

  2. 使用JavaScript添加点击事件。

  3. 使用MagicWebViewWebP提供UIWebView加载webp格式图片支持。

  4. 使用UIWebView加载HTML代码。

  5. 使用UIWebView代理方法,拦截页面发出的请求,获取selectIndex。

实现效果

组件描述说明
UIScrollView根容器高度自适应(KVO处理UIWebView + UICollectionView高度)
UIWebView图文混排展示加载HTML代码
UICollectionView更多推荐展示
 

123.gif

实现效果

问题汇总

1、如何实现JavaScript与Objective-C间传值?

点击Webview中的图片,放大,需要JavaScript和Objective-C传值,获取到具体需要放大哪张图片。

本方案中,不需要引入WebViewJavascriptBridge,而是通过【控制Webview重定向方法,拦截发出的请求】来实现。

示例:

// 每个添加点击事件(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;
}


UIWebView自适应高度的方案有很多,选择一个较为科学的方式,显得尤为重要。2、如何实现UIWebView高度自适应?

本方案中,通过【KVO监听Webview的contentSize】来实现,需要注意KVO的添加、移除,稍有不慎有Crash风险。

示例:

// 添加监听

[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"];

}


UIWebView、WKWebview本身都不支持webp格式图片,需要额外扩展。3、如何实现UIWebView显示webp格式图片?

可以直接访问我的GitHub,下载MagicWebViewWebP,将【MagicWebViewWebP.framework】直接导入工程。

参考: UIWebView、WKWebView支持WebP图片显示

示例:

// 导入头文件

#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组件,具体逻辑如下:

1.png

5、如何自定义HTML代码?

本方案中,以纯图片为例,处理后的HTML如下:

1

                                    ......

6、如何实现并发执行多个网络请求,统一处理?

本方案中,利用GCD创建队列组,提交多个任务到队列组,多个任务同时执行,监听队列组执行完毕,在主线程刷新UI。

注意: dispatch_group_enter() 、 dispatch_group_leave()将队列组中的任务未执行完毕的任务数目加减1(两个函数要配合使用)

参考: 玩转GCD

示例:

- (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

#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) iddelegate;

- (instancetype)initWithFrame:(CGRect)frame productDetailModel:(ProductDetailModel *)productDetailModel picTextModel:(ProductLoadMorePicTextModel *)picTextModel;

- (void)reload;

@end

 

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;

}

@end


ProductLoadMoreViewController中,保证两个接口都请求完成后,刷新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:@"加载中..."];

    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

Demo
https://github.com/Luis-X/MagicCubeKit

原文链接http://www.code4app.com/blog-865196-4018.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值