(iOS)电商详情(一)

商城在线更新了一波,这次更新修复了部分细微Bug,同时新增商品点击详情界面分享,工具,属性选择,一系列跟转场动画有关的功能,就这次更新,我分析下我项目中的电商详情。

GIF图奉上
商品点击
详情点击事件
商品属性选择

分析步骤

  • 骨架搭建

  • 初步逻辑处理

  • 数据和点击事件处理

  • 商品详情点击、选择、记录

  • 复杂逻辑处理

废话几句,很容易看出来CDDMall很多界面参考的是国美,详情也不例外。点击商品进入详情界面,很多人会一眼就看出UIScrollView或者UICollectionView为父控制器,内部添加子控制器来展示不同UITableView,UICollectionView,WKWebView,自定义View等,这个思路足够我们用来开发这个需求,接下来我就开始分析具体步骤,废话结束。
那就很棒了

一、骨架搭建

简易分析图

可以通过箭头很直观的看出来黑色代表根控制器,紫色是UIScrollView,灰色是UIScrollView的contentSize属性,橘色是显示出的最终界面。我通过先在详情界面上懒加载一个UIScrollView,让他的contentSize的width属性等于子控制器个数 * 整个屏幕的宽度,然后在商品的自控制上,在懒加载一个UIScrollView,设置他的contentSize属性的height等于他的子控制器的个数 * 整个屏幕的高度

部分实现代码

#pragma mark - LazyLoad
- (UIScrollView *)scrollerView
{
    if (!_scrollerView) {
        _scrollerView = [[UIScrollView alloc] initWithFrame:CGRectZero];
        _scrollerView.frame = self.view.bounds;
        _scrollerView.showsVerticalScrollIndicator = NO;
        _scrollerView.showsHorizontalScrollIndicator = NO;
        _scrollerView.contentSize = CGSizeMake(self.view.dc_width * self.childViewControllers.count, 0);
        _scrollerView.pagingEnabled = YES;
        _scrollerView.delegate = self;
        [self.view addSubview:_scrollerView];
    }
    return _scrollerView;
}

#pragma mark - LazyLoad
- (UIScrollView *)scrollerView
{
    if (!_scrollerView) {
        _scrollerView = [[UIScrollView alloc] initWithFrame:CGRectZero];
        _scrollerView.frame = self.view.bounds;
        _scrollerView.contentSize = CGSizeMake(ScreenW, (ScreenH - 50) * 2);
        _scrollerView.pagingEnabled = YES;
        _scrollerView.scrollEnabled = NO;
        [self.view addSubview:_scrollerView];
    }
    return _scrollerView;
}

添加子控制器和View

#pragma mark - 添加子控制器
-(void)setUpChildViewControllers
{
    __weak typeof(self)weakSelf = self;
    DCGoodBaseViewController *goodBaseVc = [[DCGoodBaseViewController alloc] init];
    goodBaseVc.goodTitle = _goodTitle;
    goodBaseVc.goodPrice = _goodPrice;
    goodBaseVc.goodSubtitle = _goodSubtitle;
    goodBaseVc.shufflingArray = _shufflingArray;
    goodBaseVc.goodImageView = _goodImageView;
    goodBaseVc.changeTitleBlock = ^(BOOL isChange) {
        if (isChange) {
            weakSelf.title = @"图文详情";
            weakSelf.navigationItem.titleView = nil;
                self.scrollerView.contentSize = CGSizeMake(self.view.dc_width, 0);
        }else{
            weakSelf.title = nil;
            weakSelf.navigationItem.titleView = weakSelf.bgView;
            self.scrollerView.contentSize = CGSizeMake(self.view.dc_width * self.childViewControllers.count, 0);
        }
    };
    [self addChildViewController:goodBaseVc];
    
    DCGoodParticularsViewController *goodParticularsVc = [[DCGoodParticularsViewController alloc] init];
    [self addChildViewController:goodParticularsVc];
    
    DCGoodCommentViewController *goodCommentVc = [[DCGoodCommentViewController alloc] init];
    [self addChildViewController:goodCommentVc];
}

#pragma mark - 添加子控制器View
-(void)addChildViewController
{
    NSInteger index = _scrollerView.contentOffset.x / _scrollerView.dc_width;
    UIViewController *childVc = self.childViewControllers[index];
    
    if (childVc.view.superview) return; //判断添加就不用再添加了
    childVc.view.frame = CGRectMake(index * _scrollerView.dc_width, 0, _scrollerView.dc_width, _scrollerView.dc_height);
    [_scrollerView addSubview:childVc.view];
    
}

二、初步逻辑处理

导航栏顶部按钮点击,滑动界面顶部按钮的相应

头部点击事件处理

#pragma mark - 头部按钮点击
- (void)topBottonClick:(UIButton *)button
{
button.selected = !button.selected;
[_selectBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[button setTitleColor:[UIColor redColor] forState:UIControlStateNormal];

    _selectBtn = button;
    
    __weak typeof(self)weakSelf = self;
    [UIView animateWithDuration:0.25 animations:^{
        weakSelf.indicatorView.dc_width = button.titleLabel.dc_width;
        weakSelf.indicatorView.dc_centerX = button.dc_centerX;
    }];
    
    CGPoint offset = _scrollerView.contentOffset;
    offset.x = _scrollerView.dc_width * button.tag;
    [_scrollerView setContentOffset:offset animated:YES];
}

#pragma mark - <UIScrollViewDelegate>
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
    [self addChildViewController];
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    NSInteger index = scrollView.contentOffset.x / scrollView.dc_width;
    UIButton *button = _bgView.subviews[index];
    
    [self topBottonClick:button];
    
    [self addChildViewController];
}

三、数据和点击事件处理

数据

数据没什么好说的,还是延续之前手写Plist,


资源数据数据Plist目录

可以到这个目录下去看,为什么能更方便的找到详情的代码我将整个详情抽了出来,单独放在最外层目录


商品详情目录

点击事件处理

在橘色的商品,详情,评价界面中,我们发现,点击商品的某一个cell会跳转到详情或者评价界面,通过发通知来实现的,具体代码如下,还有包括点击弹出地址的更换

#pragma mark - 滚动到详情页面
- (void)scrollToDetailsPage
{
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{//异步发通知
        [[NSNotificationCenter defaultCenter]postNotificationName:@"scrollToDetailsPage" object:nil];
    });
}

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { //评论Cell点击
    
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        [[NSNotificationCenter defaultCenter]postNotificationName:@"scrollToCommentsPage" object:nil];
    });
}

#pragma mark - 接受通知
- (void)acceptanceNote
{
    //滚动到详情
    __weak typeof(self)weakSlef = self;
    _dcObserve = [[NSNotificationCenter defaultCenter]addObserverForName:@"scrollToDetailsPage" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        [weakSlef topBottonClick:weakSlef.bgView.subviews[1]]; //跳转详情
    }];
    
    _dcObserve = [[NSNotificationCenter defaultCenter]addObserverForName:@"scrollToCommentsPage" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        [weakSlef topBottonClick:weakSlef.bgView.subviews[2]]; //跳转到评论界面
    }];
}
顶部按钮和通知点击事件处理
#pragma mark - 更换地址
- (void)chageUserAdress
{
    _adPickerView = [AddressPickerView shareInstance];//单例
    [_adPickerView showAddressPickView];
    [self.view addSubview:_adPickerView];
    
    __weak typeof(self)weakSelf = self;
    _adPickerView.block = ^(NSString *province,NSString *city,NSString *district) {
        DCUserInfo *userInfo = UserInfoData;
        NSString *newAdress = [NSString stringWithFormat:@"%@ %@ %@",province,city,district];
        if ([userInfo.defaultAddress isEqualToString:newAdress]) {
            return;
        }
        userInfo.defaultAddress = newAdress;
        [userInfo save]; //本地数据保存
        [weakSelf.collectionView reloadData];
    };
}

接下来是重点,商品属性的选择

之前有写过一个简单的商品属性选择,没有逻辑单纯的实现,如需请先参考两种思路下的属性选择

属性选择界面

四、商品详情点击、选择、记录(较为复杂的逻辑包含其中)

第一步首先是弹出属性筛选的控制器,分两种情况:

  • 第一种点击请选择商品属性Cell;
  • 第二种是在上一次未选择商品的属性情况下点击右下角的加入购物车或立即购买

代码如下

//加入购物车或点击直接购买通知
_dcObj = [[NSNotificationCenter defaultCenter]addObserverForName:@"ClikAddOrBuy" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
    
    DCFeatureSelectionViewController *dcFeaVc = [DCFeatureSelectionViewController new];
    __weak typeof(self)weakSelf = self;
    dcFeaVc.userChooseBlock = ^(NSMutableArray *seleArray, NSInteger num, NSInteger tag) { //第一次更新选择的属性
        NSString *result = [NSString stringWithFormat:@"%@ %zd件",[seleArray componentsJoinedByString:@","],num];
        weakSelf.cell.contentLabel.text = result;
    };
    
    if ([weakSelf.cell.leftTitleLable.text isEqual:@"已选"]) {
        
        if ([note.userInfo[@"buttonTag"] isEqualToString:@"2"]) { //加入购物车
            
        }else if ([note.userInfo[@"buttonTag"] isEqualToString:@"3"]){//立即购买

            DCFillinOrderViewController *dcFillVc = [DCFillinOrderViewController new];
            [weakSelf.navigationController pushViewController:dcFillVc animated:YES];
        }
        
    }else{
        dcFeaVc.goodImageView = _goodImageView;
        [self setUpAlterViewControllerWith:dcFeaVc WithDistance:ScreenH * 0.8 WithDirection:XWDrawerAnimatorDirectionBottom WithParallaxEnable:YES WithFlipEnable:YES];
    }

}];

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section == 0 && indexPath.row == 0) {
        [self scrollToDetailsPage]; //滚动到详情页面
    }else if (indexPath.section == 2 && indexPath.row == 0) {
        [self chageUserAdress]; //跟换地址
    }else if (indexPath.section == 1){ //属性选择
        DCFeatureSelectionViewController *dcFeaVc = [DCFeatureSelectionViewController new];
        __weak typeof(self)weakSelf = self;
        
        dcFeaVc.userChooseBlock = ^(NSMutableArray *seleArray, NSInteger num, NSInteger tag) { //更新选择的属性
            if (lastSeleArray_ == seleArray && lastNum_ == num)return; //传递过来的数据判断与上一次的相同则返回
            NSString *result = [NSString stringWithFormat:@"%@ %zd件",[seleArray componentsJoinedByString:@","],num];
            weakSelf.cell.contentLabel.text = result;
            lastNum_ = num;
            lastSeleArray_ = seleArray;
            if (tag == 0) {
                
            }else if(tag == 1){
                DCFillinOrderViewController *dcFillVc = [DCFillinOrderViewController new];
                [weakSelf.navigationController pushViewController:dcFillVc animated:YES];
            }
            
            if ([weakSelf.cell.leftTitleLable.text isEqualToString:@"已选"])return;
            weakSelf.cell.leftTitleLable.text = @"已选";
        };
        dcFeaVc.lastNum = lastNum_;
        dcFeaVc.lastSeleArray = lastSeleArray_;
        dcFeaVc.goodImageView = _goodImageView;
        [self setUpAlterViewControllerWith:dcFeaVc WithDistance:ScreenH * 0.8 WithDirection:XWDrawerAnimatorDirectionBottom WithParallaxEnable:YES WithFlipEnable:YES];
    }
}

接下来我们到弹出属性的控制器上,我们初始化属性数组的时候一定记得判断上一次选择的属性的数组是否存在,如果有上一次用户选择的属性记录我们需要在初始化的时候,将上一次的选择产展示处理,即现在选中状态,包括数量。
不仅仅如此,我还需要在传递出去的userChooseBlock中对用户这次的已选数据和上一次选择数据进行判断,已经选择过程中是否已经选择完毕,按钮的是否可以点击判断,进过一层层的判断这样才能保证传递出去的数据是符合需求的。

代码如下

首先是_lastSeleArray存在的情况下反向遍历初始化还原上一次用户的选择

if (_lastSeleArray.count == 0) return;
for (NSString *str in _lastSeleArray) {//反向遍历
    for (NSInteger i = 0; i < _featureAttr.count; i++) {
        for (NSInteger j = 0; j < _featureAttr[i].list.count; j++) {
            if ([_featureAttr[i].list[j].infoname isEqualToString:str]) {
                _featureAttr[i].list[j].isSelect = YES;
                [self.collectionView reloadData];
            }
        }
    }
}

其次是层层判断,按钮的点击和回调

#pragma mark - 底部按钮点击
- (void)buttomButtonClick:(UIButton *)button
{
    if (_seleArray.count != _featureAttr.count && _lastSeleArray.count != _featureAttr.count) {//未选择全属性警告
        [SVProgressHUD showInfoWithStatus:@"请选择全属性"];
        [SVProgressHUD setDefaultStyle:SVProgressHUDStyleDark];
        [SVProgressHUD dismissWithDelay:1.0];
        return;
    }
    
    [self dismissFeatureViewControllerWithTag:button.tag];
    
}

#pragma mark - 退出当前界面
- (void)dismissFeatureViewControllerWithTag:(NSInteger)tag
{
    __weak typeof(self)weakSelf = self;
    [weakSelf dismissViewControllerAnimated:YES completion:^{
        if (![weakSelf.cell.chooseAttLabel.text isEqualToString:@"有货"]) {//当选择全属性才传递出去
            if (_seleArray.count == 0) {
                NSMutableArray *numArray = [NSMutableArray arrayWithArray:_lastSeleArray];
                !weakSelf.userChooseBlock ? : weakSelf.userChooseBlock(numArray,num_,tag);
            }else{
                !weakSelf.userChooseBlock ? : weakSelf.userChooseBlock(_seleArray,num_,tag);
            }
        }
    }];
}

最后是在我们退出选择属性控制的时候,我们在拿到数据的时候也是需要判断的。情况有属性未选择完毕点击叉叉退出界面,选择完毕不存在上一次选择数据,存在上一次的选择数据,以及这一次返回的数据和上一次是都相等。

DCFeatureSelectionViewController *dcFeaVc = [DCFeatureSelectionViewController new];
__weak typeof(self)weakSelf = self;

dcFeaVc.userChooseBlock = ^(NSMutableArray *seleArray, NSInteger num, NSInteger tag) { //更新选择的属性
    if (lastSeleArray_ == seleArray && lastNum_ == num)return; //传递过来的数据判断与上一次的相同则返回
    NSString *result = [NSString stringWithFormat:@"%@ %zd件",[seleArray componentsJoinedByString:@","],num];
    weakSelf.cell.contentLabel.text = result;
    lastNum_ = num;
    lastSeleArray_ = seleArray;
    if (tag == 0) {
        
    }else if(tag == 1){
        DCFillinOrderViewController *dcFillVc = [DCFillinOrderViewController new];
        [weakSelf.navigationController pushViewController:dcFillVc animated:YES];
    }
    
    if ([weakSelf.cell.leftTitleLable.text isEqualToString:@"已选"])return;
    weakSelf.cell.leftTitleLable.text = @"已选";
};
dcFeaVc.lastNum = lastNum_;
dcFeaVc.lastSeleArray = lastSeleArray_;
dcFeaVc.goodImageView = _goodImageView;
[self setUpAlterViewControllerWith:dcFeaVc WithDistance:ScreenH * 0.8 WithDirection:XWDrawerAnimatorDirectionBottom WithParallaxEnable:YES WithFlipEnable:YES];

更新总结

  • 以上的本次更新就先说到这里,项目有时间仍会继续更新,希望大家有需要的话可以去GitHub上下载Demo,结合源码看分析我觉得效果会更好。

  • 项目写完我自己也很少测试,肯定会有很多不完善的地方,发现后会陆续完善。

  • GitHub传送门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • //我所经历的大数据平台发展史(三):互联网时代 • 上篇http://www.infoq.com/cn/arti...
    葡萄喃喃呓语阅读 51,180评论 10 200
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 七月的中旬,一艘大型游船在浩瀚的大海上航行,远方的天空在夕阳的裹携下呈现玫瑰色,镶有温和的金边。叶恩斜倚着船上蓝色...
    弥生不知鹿阅读 426评论 0 0
  • 季夏是闺蜜们的生日月,本来都是小胖寿星们许愿的,结果大家都惯着我,反到圆了我的夙愿,让我发现原来世界很精...
    宁楠阅读 452评论 0 1