v2panda的技术专题不明觉厉iOSiOS小项目

iOS高仿下厨房(Objective-C)

2016-05-02  本文已影响22643人  _Sven

2017.5.19编辑:因为官方接口变动,所以一些需要根据返回数据进行动态调整布局的地方会崩溃,如果有兴趣可以自行抓取最新接口,然后根据返回数据进行调整。

前言

本开源项目讲解了一些App常见功能界面的搭建以及实现思路,适合新手。

为什么是下厨房?

下厨房:一个集合了工具、社区与平台电商属性的家庭美食入口。很棒的一个平台,App界面也很好看!

关于项目(Github地址在文章结尾)


效果预览

首页.gif 关注动态.gif 菜谱.gif 帖子与作品.gif 商品界面.gif 商品分类选择.gif 商品界面-图片展示动画.gif 购物车.gif 上传作品.gif 收货地址.gif 搜索界面.gif 菜谱-创建与删除.gif

一、首页

首页.png
布局
思路:

顶部导航部分点击事件,通过给每个控件绑定tag,然后定义对应的枚举变量,通过闭包(Block)将事件传递到控制器后,控制器判断枚举值即可。


1. 跳转的界面控制器

① 菜谱

布局
菜谱 - Header.png 菜谱 - 作品展示.png 菜谱 - 底部.png

② 作品

作品.png
布局

2. 导航

① 关注动态

关注动态.gif
布局
遇到的问题
// scrollView停止滚动后记录contentOffset.x
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    !self.imageViewDidScrolledBlock ? : self.imageViewDidScrolledBlock(scrollView.contentOffset.x);
}
- (void)setImageViewCurrentLocation:(CGFloat)imageViewCurrentLocation {
    _imageViewCurrentLocation = imageViewCurrentLocation;
    
    // 恢复显示collectionView滚动的位置
    [self.collectionView setContentOffset:CGPointMake(imageViewCurrentLocation, 0)];

    // 恢复显示pageLabel的下标
    if (!self.pageLabel.hidden && self.imageArray.count) {
        NSInteger currentIndex = imageViewCurrentLocation / self.collectionView.frame.size.width + 1;
        self.pageLabel.text = [NSString stringWithFormat:@"%zd/%zd", currentIndex, self.imageArray.count];
    }
}

② 三餐

作品.png
布局

这个界面的接口号称“时时死”,如果想看效果的童鞋可以自己重新抓包


3. 功能界面

① 菜谱草稿(整个项目最难的界面)

菜谱创建 - 上半部分.png 菜谱创建 - 下半部分.png
布局

如图所示即可,需要注意的是:因为这个界面是操作本地数据,所以要时刻根据数据得变化判断控件是否显示、如何显示

思路
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {
    // 如果是顶部大图
    if (picker == self.headerPicker) {
        self.createRecipe.image = info[UIImagePickerControllerEditedImage];
    }
    // 如果是步骤图
    else if (picker == self.instructPicker) {
        self.instructionArray[self.setImageIndex].image = info[UIImagePickerControllerEditedImage];
    }
    [self.tableView reloadData];
    [picker dismissViewControllerAnimated:YES completion:^{
        // 选取完成后更新本地数据
        [self updateDarft];
    }];
}
草稿界面-做法.gif
// 增加一行点击回调
instructionFooter.addInstructionBlock = ^{
    // 添加一个空的本地数据
    [weakSelf.instructionArray addObject:[[XCFCreateInstruction alloc] init]];
    NSInteger row = weakSelf.instructionArray.count - 1;
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:1];
    // 插入cell
    [weakSelf.tableView insertRowsAtIndexPaths:@[indexPath]
                              withRowAnimation:UITableViewRowAnimationBottom];
};
// 如果不是步骤数组,就回到对应位置
- (NSIndexPath *)tableView:(UITableView *)tableView
targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath
       toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath {
    NSIndexPath *finalIndexPath;
    // 如果拖动到第0组,那么松手就插入第1组第0个
    if (proposedDestinationIndexPath.section == 0) {
        finalIndexPath = [NSIndexPath indexPathForRow:0 inSection:1];
    }
    // 如果拖动到第1组,松手即插入目标位置
    else if (proposedDestinationIndexPath.section == 1) {
        finalIndexPath = proposedDestinationIndexPath;
    }
    // 如果拖动到第2组,那么松手就插入第1组最后一个
    else if (proposedDestinationIndexPath.section == 2) {
        finalIndexPath = [NSIndexPath indexPathForRow:self.instructionArray.count-1 inSection:1];
    }
    return finalIndexPath;
}
草稿界面-用料.gif

② 搜索

搜索界面.gif 搜索界面.png
布局
思路

③ 上传作品

上传作品.gif
布局

一个只有tableHeaderView的tableViewController搞定,官方App中图片、标签的添加会有动画,我这里没有实现,大概就是在改变控件frame值时添加动画即可

思路

二、市集

1. 商品

商品界面.png
布局
商品界面.gif 商品界面-评价.gif
思路
// 向上拖动到一定程度,切换至图文详情界面
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    // 预定值为100
    if (scrollView.contentOffset.y > self.tableView.contentSize.height - self.tableView.frame.size.height + 100) {
        // 隐藏商品信息
        self.tableView.hidden = YES;
        // 动画
        [UIView animateWithDuration:0.5 animations:^{
            self.tableView.transform = CGAffineTransformMakeTranslation(0, -(self.view.bounds.size.height-44-64));
            self.imageTextView.transform = CGAffineTransformMakeTranslation(0, -(self.view.bounds.size.height-44));
        } completion:^(BOOL finished) {
            [UILabel showStats:@"未解决webView导致的内存泄漏问题" atView:self.view];
        }];
    }
}

2. 购物车

购物车.gif
布局
思路(我设置了清空购物车就重新加载本地数据)
    cell.itemNumberChangeBlock = ^(NSUInteger number) { // 修改商品个数回调
        // 拿到最新的数据,再修改数量
        // 如果不拿到最新数据,在编辑商品数量时点击店铺全选 会导致正在编辑的商品无法同步选中状态的bug
        NSArray *newShopArray = [XCFCartItemTool totalItems][indexPath.section];
        XCFCartItem *newItem = newShopArray[indexPath.row];
        // 修改数据中商品个数的值
        newItem.number = number;
        // 更新本地数据
        [XCFCartItemTool updateItemAtIndexPath:indexPath withItem:newItem];
        // 刷新界面
        [weakSelf.tableView reloadData];
    };

3. 确认订单

订单 - 上半部分.png 订单 - 下半部分.png
布局

如图,需要注意的是:如果计算结果店铺优惠价格为0,就会隐藏店铺优惠

思路

三、社区

社区.gif
思路

四、我

(因为我什么数据也没有,就没做那些详细界面了)

1. 个人资料

个人界面.gif

2. 收货地址

收货地址.gif
思路

本地数据工具类,修改内容闭包回调控制器更改数据,内部处理好逻辑关系就可以了


五、动画

① 图片展示

商品界面-图片展示动画.gif
思路

界面是一个UIViewController,提供接口接收数据,view中添加一个图片轮播器,present出现后执行动画(这里我只是实现了效果,详细控件分布就不做那么仔细了)
大概步骤:

  1. 点击图片
  2. 闭包回调传递 图片在当前窗口的frame值图片所在数组的下标给控制器
  3. 控制器将数值传递给图片展示控制器,并present
  4. 图片展示控制器接收图片数据赋值给图片轮播器,然后创建一个imageView(作动画用),frame设置为从上一个界面接收到的数值,然后imageView执行形变动画
  5. 动画执行完毕移除imageView,显示图片轮播器
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    
    // 关闭按钮
    UIButton *dismissButton = [[UIButton alloc] initWithFrame:CGRectMake(15, 15, 30, 30)];
    [dismissButton setImage:[UIImage imageNamed:@"closeLandscape"] forState:UIControlStateNormal];
    [dismissButton addTarget:self action:@selector(close) forControlEvents:UIControlEventTouchUpInside];
    dismissButton.alpha = 0;
    [self.view addSubview:dismissButton];
    
    // 图片轮播器
    CGRect displayRect = CGRectMake(0, XCFScreenHeight*0.5-175, XCFScreenWidth, 350);
    XCFImageShowView *showView = [[XCFImageShowView alloc] initWithFrame:displayRect];
    // 设置属性
    showView.type = XCFShowViewTypeDetail;
    showView.imageArray = self.imageArray;
    showView.currentIndex = self.imageIndex;
    showView.imageViewDidScrolledBlock = self.imageViewDidScrolledBlock;
    // 默认先隐藏
    showView.hidden = YES;
    [self.view addSubview:showView];
    
    // 临时添加一个imageView 作动画
    CGRect rect = [self.rectValue CGRectValue];
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:rect];
    XCFReviewPhoto *photo = self.imageArray[self.imageIndex];
    [imageView sd_setImageWithURL:[NSURL URLWithString:photo.url]];
    [self.view addSubview:imageView];
    
    // 动画
    [UIView animateWithDuration:0.3 animations:^{
        imageView.frame = displayRect;
    } completion:^(BOOL finished) {
        // 移除动画的imageView
        [imageView removeFromSuperview];
        // 显示图片轮播器
        showView.hidden = NO;
        [UIView animateWithDuration:0.3 animations:^{
            dismissButton.alpha = 1;
        }];
    }];
}

② 添加商品到购物车

商品分类选择.gif 商品分类view.png
布局
思路
    // 添加完成后发送通知,用处:购物车图标动画
    [[NSNotificationCenter defaultCenter] postNotificationName:XCFCartItemTotalNumberDidChangedNotification
                                                        object:nil
                                                      userInfo:@{@"goodsCount" : @([self totalNumber])}];
- (void)awakeFromNib {
    // 监听“添加商品到购物车”的通知
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(cartItemTotalNumberDidChanged:)
                                                 name:XCFCartItemTotalNumberDidChangedNotification
                                               object:nil];
    NSUInteger count = [XCFCartItemTool totalNumber];
    if (count) { // 有商品才显示数量标签
        self.countButton.hidden = NO;
        [self.countButton setTitle:[NSString stringWithFormat:@"%zd", count]
                          forState:UIControlStateNormal];
    }
}

- (void)cartItemTotalNumberDidChanged:(NSNotification *)note {
    self.countButton.hidden = NO;
    NSDictionary *dict = note.userInfo;
    // 取得商品数量
    NSUInteger count = [dict[@"goodsCount"] integerValue];
    // 延时作动画
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [UIView animateWithDuration:0.5 animations:^{
            // 购物车图标执行放大动画
            self.countButton.transform = CGAffineTransformMakeScale(1.5, 1.5);
        } completion:^(BOOL finished) {
            // 改变显示的商品数
            [self.countButton setTitle:[NSString stringWithFormat:@"%zd", count]
                              forState:UIControlStateNormal];
            // 还原图标大小
            [UIView animateWithDuration:0.5 animations:^{
                self.countButton.transform = CGAffineTransformMakeScale(1, 1);
            }];
        }];
    });
}
        // 随机添加一样商品
        XCFCartItem *randomItem = [XCFCartItemTool randomItem];
        XCFGoods *randomGoods = randomItem.goods;
        // 加入购物车
        if (type == BottomViewClickedAddToShoppingCart) {
            // 如果该商品有多种类型,就弹窗让用户选择具体购买哪种类型
            if (randomGoods.kinds.count > 1) {
                UIWindow *window = [UIApplication sharedApplication].keyWindow;
                // 缩小当前界面
                [UIView animateWithDuration:0.3 animations:^{
                    window.rootViewController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);
                }];
                
                // 显示商品分类view
                XCFKindsCategoryView *kindsView = [[XCFKindsCategoryView alloc] initWithFrame:window.bounds];
                // 分类view的弹出类型(购物车)
                kindsView.type = XCFKindsViewTypeCart;
                kindsView.item = randomItem;
                [window addSubview:kindsView];
                // 确认购买回调
                kindsView.confirmBlock = ^(XCFCartItem *item) {
                    // 本地购物车数据添加商品
                    [XCFCartItemTool addItem:item];
                    [UILabel showStats:[NSString stringWithFormat:@"添加:\n%@", item.kind_name] atView:weakSelf.view];
                };
                // 取消回调
                kindsView.cancelBlock = ^{
                    // 恢复界面大小
                    [UIView animateWithDuration:0.3 animations:^{
                        window.rootViewController.view.transform = CGAffineTransformMakeScale(1, 1);
                    }];
                };
                
            } else { // 如果只有一个商品,直接加入购物车
                [XCFCartItemTool addItemRandomly:^(NSString *goodsName) {
                    [UILabel showStats:[NSString stringWithFormat:@"随机添加:\n%@", goodsName] atView:weakSelf.view];
                }];
            }
        }

最后想说的话


Github代码下载地址

高仿下厨房App 开源咯~

END

上一篇下一篇

猜你喜欢

热点阅读