商城在线更新了一波,这次更新修复了部分细微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,
可以到这个目录下去看,为什么能更方便的找到详情的代码我将整个详情抽了出来,单独放在最外层目录
点击事件处理
在橘色的商品,详情,评价界面中,我们发现,点击商品的某一个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传送门