iOS项目 LJKitchen(仿下厨房) & Objective-C

引言:

人生真的很奇妙。

我高中时化学成绩奇差无比。印象中虽然不至于不及格,但反正也好不到哪去,如果那时候有朋友问我 “你高考志愿要不要填个化学?”, 我肯定会用那些满是红X的化学讲义糊他一脸。
是的,然后我大学学的是化工……

同样的,如果是两年前,有人问我 “你这份工作没前途,转个码农吧!”,我肯定也会嗤之以鼻,原因很简单:在我浅薄的认知范围内,搞IT的不说都是人中龙凤吧,最起码也得是逻辑思维缜密,高数高分,英语专八的精英。再看看自己,英语磕磕绊绊,高数靠补考(也有可能是老师可怜我...)才勉强及格的货色,是当码农的料么?
是的,然后我现在在写代码(bug)……

实话在我写下这段文字的时候整个脑回路还是有点蒙圈的,当初怎么走上reroll这条道路的已经不重要了,我也不敢、更不能说自己就reroll成功了,充其量也就是个码畜,说不定连码畜也不算,但我自认为最重要的一点就是在这期间我没有放弃吧(阶段性的“自我放假”倒是常有),当然,从本质上来说这也只是感动自己而已……扯远了,套用之前在水区看到的一句话:“我说来不及,你就不学了么?”,对我来说,犹如至理名言。
** 人总是要有点梦想的,万一实现了呢!**


项目介绍:

仿写目标 : [下厨房]

[AppStore 精华应用][2013年年度精选], 集成展示电商平台社交分享等多种属性于一身,受众范围非常之广,很早就喊出了 “唯美食与爱不可辜负” 这样的温暖人心【中二...】的口号,尽管现在也受到了层出不穷的同类App冲击,但依然牢牢把持着美食类应用领跑位置。

仿写目的 :

主要目的还是练手,因为无论之前为公司做的项目或者自己脑洞大开瞎整的东西,唯一的共同点就是都很蠢。同时也想看看假设面对独立开发的要求,凭自己的当前掌握的编程知识和相关理解,到底能做到什么样的程度,顺便探探自己到底能踩多少坑……

项目相关 :

环境:Xcode 7.2
作死升过一回7.3,又滚回去重新用7.2了
模式:MVC
语言:Objective-C
工具:Charles 、Snip、AALMakerLite
整个项目为纯代码布局,Masonry真乃神器!
槽点就是隔一段时间再回去看自己写的布局代码时,经常会纳闷自己当初到底怎么想的……

说明:现阶段版本已迭代至5.5,但其实项目内的绝大多数请求都是前辈在5.1.1版本时抓包获得的现成
的固定的请求。真正意义上我自己实际抓包的数据并不多,另下厨房在某次更新后也适当的对一些接口
进行了加密。不过总的来说无伤大雅,绝大部分依然可以继续使用。


项目脑图总览 (层级关系):

LJKitchen_脑图.png
  • 按照项目内的个页面的控件位置 & 客户端各个页面的层级关系绘制的该脑图。大致囊括了下厨房的各项功能。
  • 能力有限,只能以最直白的控件显示位置和页面关系作为切入点,深层一点的功能模块什么的就完全不会了,还请见谅。
  • PS:同时也没什么艺术细胞,比这更糟糕的是我偏执的认为同级主体颜色必须相同,而且相同层级标签最好能对齐。事实证明这么弄出来东西的简直丑到不能直视,凑合看吧……

项目详解:

一、首页

首页-构成结构.png
布局思路:
  • 因为没有什么附加组件,首页就是一整个tableView即可满足需求;
  • 如上图所示,导航性质的功能型组件全部集成在tableViewHeader中;
  • 以日期为单位来区分 tableViewsections
  • tableViewCell 根据功能的不同,再结合后台返回的template共有6种不同的模板 (项目中目前涉及5种)。有的比较简单(就是一整张图片),有的相对复杂一点。
组件:

tableViewHeader:


首页 - Header - 分解.png

如图所示,header主要由4大不同功能的组件构成。因为包含了位置判定、buttons、image(添加手势)等多种类型触发机制,这里使用的是为各控件绑定tag,再以tag为索引使用block回调来实现各种点击事件。

1. TopNavi - 顶部导航 :本质上还是两个 "UIImageView":
   "本周流行菜谱": 默认显示本周热度最高菜谱的主图
   "关注动态": 如果用户没有好友会显示默认的一张图片,反之则会显示好友上传的菜品/评价图片
2. NaviButtons - 导航按钮组: 4个一组的按钮,负责指引用户前往对应页面。
   // 一个月不到的时间这堆图标居然更换过两次,每次对应功能的位置还不一样……真是够了……
3. 推广 - 没什么好说的,就是推广链接或者前往优惠界面
   // 这里用的是Label,但是现在看来应该是一个填充图片比较妥当
4. ScrollView - 三餐推荐 : 从最常用的"时间段"角度出发,对菜谱进行分类,引导用户浏览
   /* 这里也挺有意思,后台返回的"popEvents"的数量会根据时间段的改变而做出相应的变化
   举个例子,如果是00:00 ~ 10:00这个时间段,只会返回1组对应数据(早餐); 反之如果是
   21:00这个时间段则会返回早、中、晚三餐的所有数据,看上去是更加人性化了,在对应时间段
   推荐给用户对应系列的菜谱。*/

tableViewCell:

首页 - Cell - 分解.png

上面说过,cell根据template的不同共分为6种不同的模板,根据返回的数据隐藏显示对应的数据。
例如:杂志模板只需要一张封面图片,而稍复杂一些的布局就如上图 --- 基本的cell布局格式。

// 模板种类:1.热门帖子 2.菜单(菜谱合辑) 3.作品展示 4.菜谱 5.杂志 6.备用(unknow)
1.Top - 顶部配图区域 
// 构成:主标题、副标题、独家标识、播放按钮等 
2.Bottom - 底部描述区域
// 构成:专辑名、姓名、描述、做过的人、评分等
3.Icon - 头像 (圆角)
层级关系:
脑图-首页.png
tableViewHeader :

TopNavi 顶部图片导航本周流行菜谱查找好友关注两项 。

本周流行菜谱 :点击跳转的是官方推荐的本周最受欢迎菜谱(最多人做 & 最高评分),和cell中的菜单模板跳转的大致相同,后面会写。
关注动态 :点击跳转的是“已关注”好友的最新动态,相当于微信朋友圈,晒图点赞评论举报等功能一应俱全,区别仅仅相对朋友圈分享包罗万象而言,这里交流的都是厨房内发生的点点滴滴。效果如下图所示:

首页_header_关注动态.gif
页面仍是一个tableView, 每个cell顶部是图片轮播器即"晒图",下部是"头像"、 "评论"、"点赞"、
"举报"等功能型组件的集合。仍然是使用block对cell上各位置的控件进行回调。
//  这里有个bug暂时还未解决,弹出alertAction时,navigationItems会发生变形。

导航按钮组中的 领券狂欢(原排行榜)菜谱分类 非常简单,都是直接跳转至对应webView即可,如下图:

跳转至webView.gif

推广 直接跳转webView或者push至优惠领取界面,效果如下:

首页_header_推广.gif

 // 写了一个假页面演示一下大概效果

三餐(scrollView) 根据点击的scrollView-page索引,跳转至对应早中晚三餐页面,效果如下:

LJK_Home_BLS_Clicked.gif

主体是一个collectionView ,底部固定一个背景有略微穿透效果(alpha == 0.8)的button
/* 这个页面的接口请求是动态的,经过测试,使用Clarles抓包获得的请求具有时效性,有效时间
   为10~30mins不等。一旦请求过期,再向服务器get数据就只能返回一个空的JSON了 */
tableViewCell :

cell模板template共6种,撇去一个可能是备用的暂时不讨论,其中热门帖子杂志这两种都是直接跳转webView即可。那么满打满算需要手动写的页面就是三种。

1.recipe(菜谱) 独立的某个菜谱;包括菜谱描述、材料清单、详细步骤、小贴士(tips)、同类作品推荐、同类作品展示等多种内容。效果如下:

首页_cell_菜谱.gif

再对这个页面进行分解:

首页—Cell-跳转菜谱-Header.png

页面构成
tableViewHeader:

Header构成 : 封面图片 + 菜谱简介 + 作者信息 + "收藏""丢进菜篮子"两个高性能性按钮
需要注意的也就是"独家"图标 和 "认证厨师"(红色厨师帽图标) 会跟根据返回数据显示/隐藏,
注意调整一下整个header的高度即可。
"丢进菜篮子"点击后会将该菜谱内的所有注明的食材及用量进行本地存储。方便用户在购买食材时查阅。

tableViewCell & tableViewHeaderFooter:

这个页面用到了5种不同格式的cell 外加3种HeaderFooter一共8种不同格式,算是比较多的了。
下面是该页面用到的可重用标识符:
static NSString *const recipeHeaderIdentifier        = @"RecipeHeader";     // Header
static NSString *const recipeFooterSupplyIdentifier  = @"SupplyFooter";     // Footer (追加描述)
static NSString *const recipeFooterAddListIdentifier = @"AddListFooter";    // Footer (加入菜单)
static NSString *const recipeIngredientIdentifier    = @"IngredientCell";   // 用料
static NSString *const recipeInstructionIdentifier   = @"InstructionCell";  // 步骤
static NSString *const recipeTipsIdentifier          = @"TipsCell";         // 小贴士
static NSString *const recipeAddToListIdentifier     = @"AddListCell";      // 被加入的菜单
static NSString *const recipeDishShowIdentifier      = @"DishShowCell";     // 作品展示

刚开始写的时候,给可重用标识符起名字都觉得一阵阵的蛋疼.png

好吧,其实虽然看上去很多,但要说难真的不至于,大部分都是固定的格式,步骤那边根据有没有步骤配图需要作出一点微调。
稍微麻烦一点的地方是 dishShowCell(作品展示),如下图所示:
首页-菜谱-作品展示.png

本质上还是一个cell,只是在其中嵌套了一个collectionView,顶部一个label显示作品数。
下面则是两个按钮,分别跳转至"所有作品"预览和"上传我的作品"进入该菜谱作品专辑。

通过实现scrollViewDelegate中的方法 完成collectionView手势左滑,松开会加载更多作品数据
(或是跳转到对应界面)的需求。代码如下:

/** scrollView滚动时,会频繁调用 */
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {

    CGFloat itemWidth = SCREEN_WIDTH * 0.6; // 宽度
    CGFloat itemLineSpacing = 10;           // 间距
    if (self.type == LJKVerticalCellTypeDish) {
        CGFloat dishContentSizeWidth = ((itemWidth + itemLineSpacing) * self.dishArray.count) - 10 + 40;
        BOOL refresh = scrollView.contentOffset.x > dishContentSizeWidth - SCREEN_WIDTH + 50;
        self.readyToRefresh = refresh;
    } else if (self.type == LJKVerticalCellTypeReview) {
        CGFloat reviewContentSizeWidth = itemWidth * 4 + 30 + 40;
        self.readyToPush = (scrollView.contentOffset.x > reviewContentSizeWidth - SCREEN_WIDTH + 50);
    }
}

/** 松开的一瞬间调用block,刷新数据 */
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    // 作品刷新
    /***  refresh = (block可用) + (readyToRefresh == YES) + (拥有可供刷新添加的数据) */
    BOOL refresh = self.refreshBlock && self.readyToRefresh && self.dishArray.count < [self.recipe.stats.n_dishes integerValue];
    !(refresh) ? : self.refreshBlock();
    
    // 跳转至"全部评价"界面
    !(self.showAllBlock && self.readyToPush) ? : self.showAllBlock();
}


// 该cell在"市集"页面中的"晒单"环节里还会继续使用,但当时没考虑到这些,所以命名上很不规范。
// "上传我做的这道菜"作为cell子控件不是很合适,应该还是做成对应的footer比较合理。
// 跟上面一样,再回头看布局和逻辑判断的时候一头雾水……根本无法理解自己当时是怎么想的……

小贴士 & 被加入的菜单

小贴士 & 被加入的菜单.png
这里相对就简单的多,每组的cell都是2个,再为对应Section添加header和footer即可
// 安卓版的官方客户端 "举报"是跳转的一个专门的用于举报的tableView

用料(Ingredient)步骤(Instruction)这两个cell都很简单,这里就不赘述。

2.dish(作品) 独立的某个作品展示,一般来说要么是与官方合作的商品试用晒单,要么是得分非常高的某道菜肴才会被收录其中。

页面效果如下图:


作品展示.png
这个界面依然是分开的,上面是图片轮播器,下面整合了许多的控件。
评论方面最多显示就是两条,想看更多讨论内容需要前往"所有评论"界面

包括作品描述 、 作品标题 、 评论内容的文字很多都是没有严格限制的,写到这的时候感觉Masonry
的优势就显示出来了。很多label只要固定好其参照,结合fontSize和预计算的宽度就可以在传入模型
时对其高度进行计算 -> 更新布局。

大部分时间,使用下面这两个NSString的扩展就可以计算出控件所需要的宽高,需要做的仅仅是规划好
各个控件的基本位置而已。

/** 类方法:根据给定文字 + 宽度 + 字号 返回size  */
+ (CGSize)getSizeWithString:(NSString*)string
                      width:(CGFloat)width
                       font:(CGFloat)font {
    CGSize size = [string boundingRectWithSize:CGSizeMake(width,MAXFLOAT)
                                    options:NSStringDrawingUsesLineFragmentOrigin
                                 attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:font]}
                                    context:nil].size;
    return size;
}

/** 类方法:根据给定文字 + 高度 + 字号 返回size  */
+ (CGSize)getSizeWithString:(NSString*)string
                     height:(CGFloat)height
                       font:(CGFloat)font {
    CGSize size = [string boundingRectWithSize:CGSizeMake(MAXFLOAT,height)
                                       options:NSStringDrawingUsesLineFragmentOrigin
                                    attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:font]}
                                       context:nil].size;
    return size;
}

// 当然,在返回包含多个控件的整体(容器)时,计算起来要略微繁琐一些。

3.list(菜单) 简单的说就是菜谱的专辑,是用户自己根据菜式时间段地域性食材食用效果(如减肥)甚至烹饪工具等标签收集整理出的一系列菜谱合辑,可以让其他用户有针对性的进行选择。效果如下:

首页_cell_菜单.gif
页面构成
仍然是一个tableView搞定
tableViewHeader用于显示"菜单名称"、"作者"、"描述"、"收藏"等内容,
控件中只有"描述"Label需要计算高度,适当更改布局即可。

每个tableViewCell布局也是固定的,点击后跳转到对应的"菜谱"即可。

tableViewHeader:

菜单header.png

tableViewCell:


菜单Cell.png
基本布局也是固定的,使用的跟首页的cell一样。
根据需求隐藏对应的控件 并更改独家图标的隐藏 / 显示即可。

二、市集

页面预览:
市集-预览
层级关系
脑图-市集.png
市集主页.png
布局思路:

观察官方App的该页面布局,上图分隔线位置都有 UITableViewStyleGrouped 效果 ,所在地区一栏为tableViewHeader,底下每一栏就是一种cell,做这个页面用了5种不同格式的cell。

header(定位):包括"自动定位"以及手动选择所在城市的功能,点击后应该跳转城市选择界面。
cell:"常用分类"、"推广(广告)"、"好店推荐"、"热门主题"、"最新上架(商品)",共5种类型。

/* 后来想想自己可能是有点进入思维误区了,因为最下方是collectionView 展示商品,
   无论把collectionView嵌套进cell还是footer总觉得怪怪的。*/
市集主页:

常用分类

常用分类.png
跟首页的"导航按钮组"类似,用九宫格布局塞满8个按钮即可(在素材库找了8个图标代替原按钮图标,
所以风格上看上去会有些突兀)。点击后push至对应页面。

推广(广告):这次学乖了,直接塞一张图片就好了……
好店推荐 热门主题 最新上架

好店推荐、 热门主题.png
因为里面的Items数量都是固定的(因为没有接口,都是假数据),就直接往cell里嵌套
collectionView了,本质上和菜谱的"dishShowCell"没什么区别,甚至可以说是更简单了。
话是这么说没错,但是为什么当初写这三玩意写了一下午呢…….png
导航条:

具体分类

市集-具体分类.png
15个item的collectionView 
// 是的,这15个item都是截图出来的,但我当初为什么要这么干呢……
购物车:

购物车有两种模式 编辑删除,通过rightNavigationItem来进行模式切换
编辑模式

购物车-编辑.gif

删除模式:

购物车-删除.gif

页面构成:

  • 整个页面是一个tableView,每个section是用户选购的某个店铺的商品(一个或多个);
  • sectionHeader 是店铺名,整体绑定一个手势,用于跳转到对应店铺;
  • sectionHeadercell 的左侧都是一个圆形的按钮,用于记录该商品是否选中
  • 底部固定一个包含全选功能的结算用View。

思路:
因为不可能获取服务器返回的购物车数据,所以购物车及订单界面的数据都是本地数据,这里使用了一个单例工具类用来保存购物车的数据,并提供数据操作方法。

业务逻辑:
-1. 为每件商品都绑定LJKCartItemState,以Selected 和 None 加以区分,记录商品的选中状态,再通过左端的圆形勾选按钮点选状态 -> block回调 -> 刷新控制器并更改本地数据。
-2. sectionHeader以及结算工具条左端的勾选按钮功能为全选,通过遍历本地数据中对应店铺内商品或所有商品,查看是否全部选中,再通过点选商品回调刷新界面即可。反之,只要tableView中有任何一个cell或header不是选中状态,就取消全选状态
-3. cell右侧的输入框供用户手动输入修改选购商品数量,通过实现-textFieldShouldEndEditing:代理方法监听用户输入的数量(1~100),通过block回传给控制器,控制器处理数据并刷新页面。
-4. 结算工具条在不同模式下显示的内容也不尽相同,编辑模式下遍历购物车内所有选中商品的数据 -> 有商品的情况下才可以进入确认订单页面。 删除模式下也是类似,只是附加一条确认用的alertAction,点击后删除对应商品数据,刷新页面。

tableViewCel内的"选中"回调及"修改商品数量"回调
    // 选中回调
    cell.selectedItemBlock = ^(LJKCartItemState state) { // 勾选商品回调
        cartItem.state = state;
        [LJKCartItemTool updateItemAtIndexPath:indexPath withItem:cartItem];
        [weakSelf.tableView reloadData];
    };
    
    // 修改商品数量回调
    cell.itemNumberChangeBlock = ^(NSUInteger number) { // 修改商品个数回调
        
        // 拿到之前保存的数据,再修改数量
        NSArray *newShopArray = [LJKCartItemTool totalItems][indexPath.section];
        LJKCartItem *newItem = newShopArray[indexPath.row];
        newItem.number = number;// 修改数据中商品个数的值
        [LJKCartItemTool updateItemAtIndexPath:indexPath withItem:newItem];// 更新本地数据
        [weakSelf.tableView reloadData];// 刷新界面
    };

sectionHeader 的"全选"回调及"跳转店铺"回调
    // 店铺全选回调
    header.selectedShopItemsBlock = ^(LJKCartShopState state) {
        // 同步店铺内商品与店铺的点选状态
        LJKCartItemState itemState = (LJKCartItemState)state;
        for (LJKCartItem *item in shopArray) {
            item.state = itemState;
        }
        [LJKCartItemTool updateShopArrayAtIndex:section withShopArray:shopArray];
        [weakSelf.tableView reloadData];
    };
    
    // 店铺Header 点击回调
    header.shopHeaderDidClickedBlock = ^(){
        [weakSelf.navigationController pushViewController:[[LJKShopViewController alloc] init]
                                                 animated:YES];
    };

cell本身的 - didSelectRowAtIndexPath: 方法被我用来跳转商品用了,但在实际测试的经常
点选左侧的勾选按钮或者修改数量时一不留神就跑到商品页面去了,我在想是不是应该不通过这个方法
跳转商品详情。
// 算是整个项目的重灾区,只能算是勉强实现了需求,还有不少问题没解决。
订单:
购物车-订单.gif
订单-tableViewFooter.png
  • 相对购物车而言,因为没那么多选中状态判断 娘的终于没有了,订单页面就简单的多了,跟购物车基本相同,但是每个section下面多了一个用于计算邮费、店铺内商品总价格留言的footer,拿到购物车传过来的数据,再进行计算就行了(邮费计算一次即可)。底部的付款条也不用那么麻烦了,只有一种模式。
  • 但支付方式那边被我整个弄成一个tableViewFooter了,老实说总觉得怪怪的……
  • tableViewHeader是一个收货地址显示,点击后应该跳转到收货地址管理。
商品页面:
市集-商品.gif

页面构成:
依然是一个tableView

  • 商品图片(轮播器)商品详情()店铺优惠(公告)三大块 一起拼凑成tableViewHeader。
  • 下面接一个店铺介绍cell和购买评价cell
  • 大家都在买:同类热销商品推荐。
  • 继续拖拽查看详情作为tableViewFooter
  • 底部固定一个bottomView工具条 用于前往店铺联系卖家加入商品至购物车立即购买
商品-顶部.png
商品-底部.png
  • 商品评价的内的cell和dishShowCell相同,根据LJKVerticalCellType(垂直)的不同简单的更新布局即可。
  • 继续拖动,查看详情这里应该是实现scrollView的代理方法,判断
    contentOffset.y是否达到设定的位置 -> 加载新的页面
  • 其实只做了一个框架出来,还有很多对应的功能没有实现。

三、信箱(社交)

页面预览:
信箱-预览.gif
层级关系:
脑图-信箱-各层级关系.png
布局思路:

整体页面仍然是tableView ,因为主体功能是社交,所以界面不需要太过复杂。


信箱.png
组件:

tableViewHeader: 发一封信 ,点击后跳转至搜索用户界面
tableViewCell:对话cell ,每个cell表示一段即时聊天。(未集成)
BottomView:意见反馈

意见反馈.png
输入框通过UITextView的子类来实现,添加了一个自定的placeHolder属性;
在init方法中加入通知,监听textView的变化;
在textView.text发生改变的时候 -> 通过[self setNeedsDisplay] 
-> 强制执行 - drawRect方法对TextView内的占位符进行重绘。代码如下

- (void)drawRect:(CGRect)rect {
    // 如果textView内有文字 就重新调用一次该方法,直接返回(会擦掉原有的绘制的文字)
    if (self.hasText) { 
        return;
    }
    // 设置文字属性
    NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
    attrs[NSFontAttributeName] = self.font;
    attrs[NSForegroundColorAttributeName] = self.placeholderColor ? self.placeholderColor : Color_DarkGray;
    CGFloat phX = 5;
    CGFloat phY = 8;
    CGFloat phW = rect.size.width - 2 * phX;
    CGFloat phH = rect.size.height - 2 * phY;
    CGRect phRect = CGRectMake(phX, phY, phW, phH);
    [self.placeholder drawInRect:phRect withAttributes:attrs];
}

查看帮助中心

帮助中心.png

NavigationItem 导航条按钮 : 社区推送和提醒
社区

信箱--社区.png
1. 两个cell分别跳转"市集讨论区"、"周边"
2. footer跳转"厨友社" 

社区主题(BBS)

信箱-社区.gif
讨论区-帖子.png
"社区" 控制器存在普通主题与置顶主题两种格式,需要对置顶图标重新布局
"主题" 发帖主题部分作为tableViewHeader ,每行cell 则是"跟帖",底部固定一个发送评论的工
具条,将评论内容发送至服务器,并刷新tableView。

推送和提醒

推送与提醒.png
根据cell的右视图不同,该页面有两种不同格式。
顶部cell点击后会请求授权,前往iPhone的设置界面。
其他cell右视图是一个switch控件,用于保存用户的各类偏好设置。

周边 需要使用到定位功能,暂时没做。


四、我(个人)

页面预览:
profile-预览.gif
层级关系:
脑图-个人(profile).png
组件:

header:

Profile-header.png

个人信息(detail): 用户个人信息概览
NaviButtons: 用户个人重要信息浏览
绑定手机:-> 增加用户粘性...

segment:


分页控制器.png
这里使用了第三方类库"HMSegmentedControl"
通过切换segment的title对下方的内容进行切换
// 这里也算是重灾区,一开始的思路就有些问题,导致后面不少bug,现在这里也只是一个假页面。

NavigationItem 导航条按钮 :寻找厨友设置
寻找厨友: 集成了官方推荐微博QQ控件豆瓣等推荐功能。
设置

我-设置.png

两种不同格式的cell构成,footer用来显示版本号 & "退出"功能

个人信息编辑

个人信息编辑.png
heade头像视图:  根据现今用户习惯,头像自身也绑定了手势,与下方的"上传我的头像"功能相同。
再通过UIImagePickerController 打开相册选取图片并上传、更新头像.

// 模拟器无法打开摄像头,这里暂不考虑从相机获取图片

cell具体编辑: 与RecipeViewController 类似,这里使用了一共4种不同格式的cell,分别对应:
1. 昵称编辑  
2. 生日 & 性别选择
3&4. 地址选择 
5. 个人简介(个性签名)编辑。

"昵称"和"个性签名"(1、5)原理基本相同,都是添加textView或textField进cell,
并绑定通知UITextFieldTextDidChangeNotification、UITextViewTextDidChangeNotification
并在编辑完成后通过block进行回调将textField、textView内输入的文字展示在界面上即可。

"生日&性别"、"现居"、"长居" (2、3、4)略复杂一点,相同点是都绑定了对应的 UIPickerView
或是 UIDatePicker 便于用户进行选择。

同时也自定义了一个键盘工具条(UIToolBar)作为辅助,这里同样是使用分类进行创建的。
并在init方法中绑定其对应的inputView 及 inputAccessoryView 并实现其代理方法。


// 选择图片的数据源
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
     headerPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
     [weakSelf presentViewController:headerPicker animated:YES completion:nil];
}

// 绑定对应的输入源及辅助视图
- (void)setupPickerView {
    self.locationField.inputView = self.locationPicker;
    self.locationField.inputAccessoryView = self.toolBar;
}

// UIToolBarExtension 
+ (UIToolbar *)createToolBarWithTwoButtonsBtn1:(NSString *)button1_title
                                    btn1Action:(SEL)button1_action
                                          btn2:(NSString *)button2_title
                                    btn2Action:(SEL)button2_action
                                        target:(id)target {
    
    UIToolbar *toolBar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, TABBAR_HEIGHT)];
    UIBarButtonItem *btn1 = [[UIBarButtonItem alloc] initWithTitle:button1_title
                                                             style:UIBarButtonItemStylePlain
                                                            target:target
                                                            action:button1_action];
    
    UIBarButtonItem *btn2 = [[UIBarButtonItem alloc] initWithTitle:button2_title
                                                               style:UIBarButtonItemStylePlain
                                                              target:target
                                                              action:button2_action];
    UIBarButtonItem *flexible = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
    toolBar.items = @[flexible,btn2,btn1];
    toolBar.tintColor = Color_ThemeColor;
    
    return toolBar;
}


遇到的一些坑

1. tableView: 因为首页的tableView是以日期作为sectionHeader作为单元进行分类的。
考虑到cell重用问题,这里使用的都是同一种cell,而频繁的调用setter方法显示、隐藏cell
内的控件也造成了一些卡顿现象。
// 是啊,我当时为什么不用懒加载呢……

2. navigationItem:  主要是菜谱界面(以及之后的商品界面)中的自定义navigationItems, 
因为本着重复代码就要封装的原则,将"微信分享"、"朋友圈"、"其他分享"等按钮封装起来势在必行。
但挑的路感觉错了,我使用的是 UIBarButtonItem (Extension) 分类来返回包含4个不同item
的数组,再然后就傻乎乎的想向分类中添加block回调……结果当然是失败了。查找资料后才知道向分
类中添加属性倒不是不行,只是要用到runtime机制。实话这块我学的稀烂无比,一番尝试过,最后
选择了放弃……

3. 一些零零碎碎的UI搭建 、"api接口"问题,当初困扰了很久,现在看起来都是一些很蠢的问题
/* 1> 导航按钮组某次无论怎么点都没反应,使用Capture View Hierarchy查看也没什么毛病,
   就是压根不进对应的SEL方法,最后发现是父视图的容器只设置了高度,忘记设置宽度 -> 有了
   高度就能够正常显示,但因为宽度默认为0,自然无法响应。
   2> 圆角图片的处理,略略有点钻牛角尖了,总想着使用最优解。
   3> 搭建界面时遇到的各种控件重叠现象,或是重复约束所发出的警告。
   4> 子控件的添加顺序也是会对界面产生一定的影响,如头像控件的添加顺序。
   5> 因为抓包获取的请求数据基本都是固定的,相对比较好调,基本也不存在error,但估计如果
   换成真实的需要配置params的请求就很难讲了。
   6> 在类方法里使用self而不是target:(id),蠢的要命……
   7> 某些返回数据,都是数字有些是int有些是string,看着几百亿的价格栏还是点赞人数的我是
   一头雾水,差点就开始怀疑人生…… */

4. 试过才知道系列……原来UITableViewCell可以直接直接给tableViewHeader赋值。

5. 没有计划性的写控件,命名极度不规范,驼峰和下划线混着用,写着写着自己都懵了。

6. 同上,尽管Masonry很方便,但是自己并没有理解懒加载的精髓,把所有subViews一股脑的
全塞在init方法里面,某些情况下需要更新约束时,代码就写的很不舒服,要是只是难写倒也罢了,
运行时候因为某个子控件因为找不到其对应的参照物 -> 报错的现象时有发生。

7. 重复代码还是太多了,自己只能完成一些简单的封装,稍微复杂一点的经常抓瞎。

8. 有些地方写的真的感觉很蠢……比如下面这段:
   // 返回固定格式整个leftBarButtons数组 
    self.naviButtonsArray = [UIBarButtonItem createShareButtonsWith:self
                                                             target:self
                                                  firstButtonAction:@selector(backButtonDidClicked)
                                                 secondButtonAction:@selector(PYQButtonDidClicked)
                                                  thirdButtonAction:@selector(WeChatButtonDidClicked)
                                                 fourthButtonAction:@selector(OtherButtonDidClicked)];
    self.navigationItem.leftBarButtonItems = self.naviButtonsArray;
    // 万幸只有4个item……
关于项目中很多跳转到webView的地方……
/* 项目中还是有不少地方是直接跳转到对应html界面的,开始觉得:“强!牛逼!方便!无敌!”,
   但越到后来这种感觉越发稀薄,取而代之的是挫败感越来越多……
   很简单,比方同样是菜谱展示,我发现人家网页端直接加载的比我自己辛辛苦苦瞎捣鼓出来的要好看
   的多…那么问题来了,直接全部加载webView不就完了么……还要我干什么…… */

后记:

每天稀里糊涂的敲键盘,跌跌撞撞的用了一个月时间只做到这种程度实在是很惭愧,因为有很多功能因为条件有限、难度比较高 (懒)放弃了。最后只是机械师式的搭积木搭出这样的一个四不像出来,深深的感受到自己的不足,但总的来说过程还是比较充实的,勉强也算是一次不错的历练,有这样的经历自己才能走的更远。

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

推荐阅读更多精彩内容

  • 2017.5.19编辑:因为官方接口变动,所以一些需要根据返回数据进行动态调整布局的地方会崩溃,如果有兴趣可以自行...
    _Sven阅读 27,164评论 301 459
  • 一、概览 1.产品名称: 下厨房 2.APP版本号:6.1.0 3.设备型号:Redmi Note 3 4.操作系...
    抹绿呀阅读 7,843评论 6 41
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,464评论 25 707
  • Gradle Gradle是什么 gradle是一个工具 --> 会写,会配置脚本 gradle是一个编程框架 -...
    Sparky阅读 613评论 0 0
  • 第一次看到她时是在八十年代一个初春的早晨,初升的太阳在世界力有一种透明的质感,空气微拂在脸上有说不出的惬意。远近间...
    海目青阅读 507评论 0 4