2017.5.19編輯:因?yàn)楣俜浇涌谧儎?dòng)名党,所以一些需要根據(jù)返回?cái)?shù)據(jù)進(jìn)行動(dòng)態(tài)調(diào)整布局的地方會(huì)崩潰蚪战,如果有興趣可以自行抓取最新接口担映,然后根據(jù)返回?cái)?shù)據(jù)進(jìn)行調(diào)整双饥。
前言
本開源項(xiàng)目講解了一些App常見功能界面的搭建以及實(shí)現(xiàn)思路,適合新手五续。
為什么是下廚房洒敏?
下廚房:一個(gè)集合了工具、社區(qū)與平臺(tái)電商屬性的家庭美食入口疙驾。很棒的一個(gè)平臺(tái)凶伙,App界面也很好看!
關(guān)于項(xiàng)目(Github地址在文章結(jié)尾)
- 開發(fā)環(huán)境:Xcode 7.2它碎,語言:Objective-C
- 用到的工具:Charles抓包工具
- 仿寫程度:只是作為一個(gè)練習(xí)項(xiàng)目函荣,除了沒有接口的界面显押,以及一些小細(xì)節(jié)沒有調(diào)整,其他大部分主要功能都實(shí)現(xiàn)了傻挂。其他部分童鞋們可以自己嘗試實(shí)現(xiàn)~
- 剛開始寫的時(shí)候控件是用純代碼寫的乘碑,后來發(fā)現(xiàn)太耗時(shí)間就改用Xib了
- 代碼如果有不合理的地方(如:命名),請(qǐng)見諒金拒,學(xué)習(xí)實(shí)現(xiàn)思路就好兽肤,代碼請(qǐng)不要借鑒。
- 這個(gè)開源項(xiàng)目適合新手绪抛,基本的界面布局以及業(yè)務(wù)邏輯都有轿衔,看完這個(gè)基本也會(huì)寫簡單的App啦
- 備注:因?yàn)楫?dāng)時(shí)對(duì)block有特殊偏好所以通篇沒有一個(gè)protocol...代碼也是比較新手,嘛主要以實(shí)現(xiàn)思路為主睦疫!希望能對(duì)各位有幫助害驹!
效果預(yù)覽
一、首頁
布局
- 如圖蛤育,首頁tableView就可以搞定
- 日期標(biāo)題為sectionHeader
- 下面的就是cell了宛官,下廚房返回的數(shù)據(jù)中cell有6種模板,通過自定義cell瓦糕,根據(jù)不同模板顯示不同效果底洗,很簡單就不描述了
思路:
頂部導(dǎo)航部分點(diǎn)擊事件,通過給每個(gè)控件綁定tag
咕娄,然后定義對(duì)應(yīng)的枚舉變量亥揖,通過閉包(Block)將事件傳遞到控制器后,控制器判斷枚舉值即可圣勒。
1. 跳轉(zhuǎn)的界面控制器
① 菜譜
布局
- 整個(gè)界面是一個(gè)UIViewController费变,放上一個(gè)tableView,然后添加底部的
收藏圣贸、丟進(jìn)菜籃子
自定義view - 用料挚歧、做法、小貼士吁峻、被加入的菜單這四個(gè)標(biāo)題是sectionHeader滑负,其內(nèi)容對(duì)應(yīng)為一組,每組cell自定義即可
- 作品展示:
- 這里實(shí)現(xiàn)的方法跟上面的
用料用含、做法...
一樣矮慕,獨(dú)立為一組,組內(nèi)只有一個(gè)cell啄骇,cell的contentView里從上至下添加:作品個(gè)數(shù)Label
痴鳄、作品展示CollectionView
、所有作品Button
即可 -
collectionView手勢(shì)左滑肠缔,松開會(huì)加載更多作品數(shù)據(jù)
這里通過實(shí)現(xiàn)scrollView的代理方法夏跷,判斷contentOffset
的值是否達(dá)到預(yù)定的數(shù)值哼转,達(dá)到即調(diào)用block明未,然后控制器發(fā)送網(wǎng)絡(luò)請(qǐng)求加載更多數(shù)據(jù)槽华,刷新界面即可。(這里我只是實(shí)現(xiàn)了一個(gè)需求趟妥,并沒有進(jìn)一步優(yōu)化調(diào)整)
- 布局:
如圖即可猫态,底部加入菜單button
也可以是sectionFooter,
雖然下廚房幾乎沒有邊框(有分割線)披摄,但仔細(xì)分析還是很好劃分的
② 作品
布局
- 因?yàn)?code>關(guān)注動(dòng)態(tài)亲雪、
買買買
界面跟這個(gè)差不多,需要復(fù)用到這個(gè)界面的內(nèi)容疚膊,所以這整個(gè)界面是只有一個(gè)cell
的tableView - 上面是個(gè)圖片輪播器义辕,下面添加控件即可,
描述Label
以及用戶評(píng)論Label
的高度是有內(nèi)容決定的寓盗,這個(gè)只需要在模型中添加一個(gè)labelHeight
屬性灌砖,然后在內(nèi)部計(jì)算好高度,直接返回給控件就可以了傀蚌』裕控件部分可以根據(jù)不同界面顯示的不同效果,分割成若干部分善炫,然后給cell添加一個(gè)type
屬性(枚舉類型)撩幽,創(chuàng)建的時(shí)候告訴cell屬于哪一種type
,然后根據(jù)不同type
進(jìn)行調(diào)整即可箩艺。
2. 導(dǎo)航
① 關(guān)注動(dòng)態(tài)
布局
- 整個(gè)界面就一個(gè)tableView窜醉,一個(gè)動(dòng)態(tài)為
作品界面的cell
遇到的問題
圖片輪播器會(huì)受
tableViewCell的復(fù)用機(jī)制
影響,導(dǎo)致錯(cuò)亂(點(diǎn)贊按鈕的狀態(tài)是由服務(wù)器返回的數(shù)據(jù)決定的艺谆,這里我就不模擬了)
解決辦法:在控制器中添加一個(gè)記錄圖片輪播器滾動(dòng)位置的數(shù)組屬性
imageViewCurrentLocationArray
在圖片輪播器里實(shí)現(xiàn)scrollView代理方法酱虎,監(jiān)聽記錄最終的位移
contentOffset.x
,停止?jié)L動(dòng)后通過閉包/代理將位置數(shù)據(jù)
傳遞到控制器擂涛,控制器將位置數(shù)據(jù)
添加到記錄數(shù)組
中即可读串,此方法應(yīng)該同樣適用其他因cell復(fù)用機(jī)制
導(dǎo)致的數(shù)據(jù)顯示混亂問題,(關(guān)于數(shù)組的操作撒妈,應(yīng)考慮到:下拉刷新恢暖,上拉加載更多數(shù)據(jù)以及其他情況,詳細(xì)代碼見工程)
至于實(shí)現(xiàn)哪個(gè)代理方法最為合理狰右,應(yīng)該視實(shí)際的業(yè)務(wù)需求以及界面效果而定杰捂,下面的textField代理
也是如此
// scrollView停止?jié)L動(dòng)后記錄contentOffset.x
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
!self.imageViewDidScrolledBlock ? : self.imageViewDidScrolledBlock(scrollView.contentOffset.x);
}
- 最后圖片輪播器添加一個(gè)屬性接口,接收
位置數(shù)據(jù)
棋蚌,然后在構(gòu)造方法里設(shè)置圖片輪播器的contentOffset
即可
- (void)setImageViewCurrentLocation:(CGFloat)imageViewCurrentLocation {
_imageViewCurrentLocation = imageViewCurrentLocation;
// 恢復(fù)顯示collectionView滾動(dòng)的位置
[self.collectionView setContentOffset:CGPointMake(imageViewCurrentLocation, 0)];
// 恢復(fù)顯示pageLabel的下標(biāo)
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];
}
}
② 三餐
布局
如圖嫁佳,整個(gè)控制器是
ViewController
挨队,將CollectionView
以及上傳button
添加到viewController.view
即可,比較簡單導(dǎo)航欄的標(biāo)題是自定義的view蒿往,然后
self.navigationItem.titleView = view;
即可
這個(gè)界面的接口號(hào)稱“時(shí)時(shí)死”盛垦,如果想看效果的童鞋可以自己重新抓包
3. 功能界面
① 菜譜草稿(整個(gè)項(xiàng)目最難的界面)
布局
如圖所示即可,需要注意的是:因?yàn)檫@個(gè)界面是操作本地?cái)?shù)據(jù)瓤漏,所以要時(shí)刻根據(jù)數(shù)據(jù)得變化判斷控件是否顯示腾夯、如何顯示
思路
- 因?yàn)槭莿?chuàng)建以及草稿功能的界面,所以操作的是本地?cái)?shù)據(jù)蔬充,我寫了一個(gè)
菜譜草稿數(shù)據(jù)工具類
蝶俱,用來增刪改查,非常方便 -
照片上傳:
點(diǎn)擊彈出ActionSheet
讓用戶選擇是相機(jī)
饥漫、還是相冊(cè)
榨呆,然后通過UIImagePickerController
的代理方法- imagePickerController:didFinishPickingMediaWithInfo:
選取照片即可 - 因?yàn)?code>頂部跟
做法
都有上傳圖片的需求,如果不做判斷是誰需要設(shè)置圖片庸队,會(huì)導(dǎo)致數(shù)據(jù)顯示錯(cuò)亂积蜻,這個(gè)在代理方法中通過判斷代理的調(diào)用者即可解決圖片錯(cuò)亂
- (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:^{
// 選取完成后更新本地?cái)?shù)據(jù)
[self updateDarft];
}];
}
- 做法步驟:
- 點(diǎn)擊
添加步驟
,往tableView對(duì)應(yīng)位置插入一行步驟cell
皿哨,并且在模型數(shù)據(jù)數(shù)組對(duì)應(yīng)位置插入一個(gè)數(shù)據(jù)為空的做法數(shù)據(jù)
浅侨,如果不添加的話,點(diǎn)擊編輯就會(huì)因?yàn)閿?shù)據(jù)越界導(dǎo)致崩潰
// 增加一行點(diǎn)擊回調(diào)
instructionFooter.addInstructionBlock = ^{
// 添加一個(gè)空的本地?cái)?shù)據(jù)
[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];
};
- 點(diǎn)擊
調(diào)整步驟
证膨,執(zhí)行回調(diào)后tableView進(jìn)入對(duì)應(yīng)狀態(tài)如输,[self.tableView setEditing:YES animated:YES];
,這里需要注意的問題有: - tableView中只有
做法
這一組cell才進(jìn)入編輯模式央勒,在代理方法-tableView:canMoveRowAtIndexPath:
中判斷即可 -
需要設(shè)置cell目標(biāo)移動(dòng)的位置不见,即使其他組不能進(jìn)入編輯模式,但
做法步驟cell
還是能移動(dòng)插入其中崔步,所以需要設(shè)置:如果超過了做法步驟
所在的section稳吮,不管怎么移動(dòng),最終都回到做法步驟
section - 調(diào)整移動(dòng)了cell之后井濒,也需要同步數(shù)據(jù)中對(duì)應(yīng)
做法步驟
的位置
// 如果不是步驟數(shù)組灶似,就回到對(duì)應(yīng)位置
- (NSIndexPath *)tableView:(UITableView *)tableView
targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath
toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath {
NSIndexPath *finalIndexPath;
// 如果拖動(dòng)到第0組,那么松手就插入第1組第0個(gè)
if (proposedDestinationIndexPath.section == 0) {
finalIndexPath = [NSIndexPath indexPathForRow:0 inSection:1];
}
// 如果拖動(dòng)到第1組瑞你,松手即插入目標(biāo)位置
else if (proposedDestinationIndexPath.section == 1) {
finalIndexPath = proposedDestinationIndexPath;
}
// 如果拖動(dòng)到第2組酪惭,那么松手就插入第1組最后一個(gè)
else if (proposedDestinationIndexPath.section == 2) {
finalIndexPath = [NSIndexPath indexPathForRow:self.instructionArray.count-1 inSection:1];
}
return finalIndexPath;
}
用料:
原理大致與做法步驟
相同,只是用料的編輯是在一個(gè)新的控制器XCFIngredientEditController
者甲,不管有無用料春感,點(diǎn)擊都進(jìn)入這個(gè)控制器,那么:在
XCFIngredientEditController
中添加一個(gè)接口,接收已存在的用料數(shù)據(jù)
鲫懒,如果數(shù)據(jù)為空嫩实,就默認(rèn)添加兩個(gè)示例cell
,如果不為空窥岩,就顯示已存在的數(shù)據(jù)甲献,從而達(dá)到新增、編輯的效果編輯完成或保存后谦秧,pop回
創(chuàng)建菜譜
控制器竟纳,并執(zhí)行回調(diào)將編輯好的用料數(shù)據(jù)
回傳撵溃,刷新界面即可因?yàn)楣俜降男Ч牵褐灰僮髁藬?shù)據(jù)疚鲤,即使不點(diǎn)擊
保存
,也會(huì)將數(shù)據(jù)保存起來缘挑,所以我在每個(gè)數(shù)據(jù)操作后面都更新了本地?cái)?shù)據(jù)[self updateDarft];
② 搜索
布局
- 進(jìn)入搜索控制器時(shí)集歇,判斷本地?cái)?shù)據(jù)中是否有已經(jīng)搜索過的關(guān)鍵詞,有則加載语淘,沒有就不顯示第0組cell
- 輸入文字時(shí)诲宇,利用通知監(jiān)聽
UITextFieldTextDidChangedNotification
,然后通過閉包回傳textField的文字內(nèi)容
給控制器惶翻,同時(shí)時(shí)刻刷新tableView即可 - 底部流行搜索關(guān)鍵詞是網(wǎng)絡(luò)數(shù)據(jù)加載的姑蓝,一個(gè)九宮格搞定
思路
- 因?yàn)闆]有接口,所以我封裝了一個(gè)本地單例類吕粗,保存搜索過的關(guān)鍵詞纺荧,并提供數(shù)據(jù)操作的方法
- 搜索:在本地?cái)?shù)據(jù)中遍歷是否已存在該關(guān)鍵詞,存在就將舊關(guān)鍵詞刪除颅筋,然后穿插新關(guān)鍵詞到第0位宙暇;如果不存在就直接插入到第0位即可(詳細(xì)代碼見工程)
③ 上傳作品
布局
一個(gè)只有tableHeaderView的tableViewController搞定,官方App中圖片议泵、標(biāo)簽的添加會(huì)有動(dòng)畫占贫,我這里沒有實(shí)現(xiàn),大概就是在改變控件frame值時(shí)添加動(dòng)畫即可
思路
- 本地工具類先口,并不需要進(jìn)行數(shù)據(jù)持久化型奥,通過回調(diào)就可以完成數(shù)據(jù)操作
- 需要注意的是:處理好圖片、標(biāo)簽長度的顯示以及換行
二碉京、市集
1. 商品
布局
- 上面這部分我的實(shí)現(xiàn)方法是:全部作為一個(gè)tableHeaderView厢汹,然后內(nèi)部細(xì)分為3個(gè)部分,控制好布局就可以了
- 需要注意的是:商品優(yōu)惠(紅色邊框button)收夸、店鋪優(yōu)惠(橙色)坑匠,服務(wù)器返回的是字符串類型數(shù)據(jù),這里我將優(yōu)惠信息以button展示(也可以Label)卧惜,因?yàn)閿?shù)據(jù)是動(dòng)態(tài)的厘灼,所以可以通過計(jì)算字符串最大寬度夹纫,然后加上既定的長度,即可設(shè)置每個(gè)button的不同大小
思路
- 整個(gè)控制器是UIViewController设凹,上面
商品信息展示
部分是tableView舰讹,下面圖文詳情界面
是一個(gè)UIView(UIView上面放一個(gè)類似導(dǎo)航的標(biāo)簽view
,下面為CollectionView闪朱,CollectionView的cell內(nèi)部添加tableView)月匣,設(shè)置好對(duì)應(yīng)frame即可 -
繼續(xù)拖動(dòng),查看圖文詳情:
這個(gè)通過實(shí)現(xiàn)scrollView代理方法奋姿,判斷contentOffset.y
的值是否達(dá)到預(yù)定值锄开,官方效果是:不必等到松手,達(dá)到即商品展示tableView
和圖文詳情view
同時(shí)進(jìn)行位移動(dòng)畫称诗,同時(shí)商品展示tableView.hidden = YES;
萍悴,從而達(dá)到動(dòng)畫切換界面的效果。由圖文詳情
切換回商品展示
也是通過代理實(shí)現(xiàn)
// 向上拖動(dòng)到一定程度寓免,切換至圖文詳情界面
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// 預(yù)定值為100
if (scrollView.contentOffset.y > self.tableView.contentSize.height - self.tableView.frame.size.height + 100) {
// 隱藏商品信息
self.tableView.hidden = YES;
// 動(dòng)畫
[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導(dǎo)致的內(nèi)存泄漏問題" atView:self.view];
}];
}
}
-
評(píng)價(jià)CollectionView手勢(shì)左滑切換控制器:
也是實(shí)現(xiàn)scrollView代理方法癣诱,通過回調(diào)切換控制器 -
曬圖/全部評(píng)價(jià)界面:
官方效果是:兩個(gè)tableView重疊在一起,點(diǎn)擊導(dǎo)航欄對(duì)應(yīng)按鈕設(shè)置tableView.hidden
2. 購物車
布局
- 整個(gè)控制器是UIViewController袜香,
self.view
添加tableView跟底部的結(jié)算view
- 同一個(gè)店鋪的商品作為一組cell撕予,店鋪名為sectionHeader
思路(我設(shè)置了清空購物車就重新加載本地?cái)?shù)據(jù))
- 官方是通過服務(wù)器接收數(shù)據(jù)顯示購物車內(nèi)容的,因?yàn)闆]接口所以我就新建了一個(gè)本地單例類蜈首,保存購物車的數(shù)據(jù)实抡,并提供數(shù)據(jù)操作方法
- 業(yè)務(wù)邏輯:
- 給每個(gè)商品添加一個(gè)
XCFCartItemState
枚舉類型屬性,記錄是否被選中疾就,點(diǎn)選商品勾選button
后通過回調(diào)澜术,在控制器更改本地?cái)?shù)據(jù)。 -
店鋪勾選
狀態(tài)以及全選
狀態(tài)猬腰,通過遍歷本地?cái)?shù)據(jù)中對(duì)應(yīng)店鋪內(nèi)商品
或所有商品
是否全部選中鸟废,再通過點(diǎn)選商品回調(diào)
刷新界面就可以達(dá)到實(shí)時(shí)更改狀態(tài)的效果了 -
商品購買數(shù)量:
在cell內(nèi)部實(shí)現(xiàn)textField代理方法-textFieldShouldEndEditing:
監(jiān)聽購買數(shù)量,編輯完成就通過閉包傳遞購買數(shù)量給控制器姑荷,控制器更改本地?cái)?shù)據(jù)并刷新界面
cell.itemNumberChangeBlock = ^(NSUInteger number) { // 修改商品個(gè)數(shù)回調(diào)
// 拿到最新的數(shù)據(jù)盒延,再修改數(shù)量
// 如果不拿到最新數(shù)據(jù),在編輯商品數(shù)量時(shí)點(diǎn)擊店鋪全選 會(huì)導(dǎo)致正在編輯的商品無法同步選中狀態(tài)的bug
NSArray *newShopArray = [XCFCartItemTool totalItems][indexPath.section];
XCFCartItem *newItem = newShopArray[indexPath.row];
// 修改數(shù)據(jù)中商品個(gè)數(shù)的值
newItem.number = number;
// 更新本地?cái)?shù)據(jù)
[XCFCartItemTool updateItemAtIndexPath:indexPath withItem:newItem];
// 刷新界面
[weakSelf.tableView reloadData];
};
-
切換編輯/刪除模式:
簡單的閉包回調(diào)刷新界面即可鼠冕,刪除的話添寺,根據(jù)商品狀態(tài)是否點(diǎn)選,點(diǎn)選就刪除懈费,因?yàn)辄c(diǎn)選狀態(tài)跟編輯模式是通用的计露,所以不需要另外計(jì)算
3. 確認(rèn)訂單
布局
如圖,需要注意的是:如果計(jì)算結(jié)果店鋪優(yōu)惠價(jià)格為0,就會(huì)隱藏店鋪優(yōu)惠
思路
- 直接拿到購物車界面勾選好的的商品票罐,通過計(jì)算顯示對(duì)應(yīng)價(jià)格數(shù)字就可以了(運(yùn)費(fèi)只添加一次)
- 選擇收貨地址叉趣,訪問本地存儲(chǔ)的收貨地址數(shù)據(jù),有則顯示该押,沒有則提示添加
- 因?yàn)?del>沒錢沒見過
訂單
疗杉、優(yōu)惠
長啥樣,所以就沒做了蚕礼,不過應(yīng)該就是tableView就可以搞定的
三烟具、社區(qū)
思路
- 點(diǎn)選一個(gè)評(píng)論cell,就獲取該評(píng)論作者的昵稱奠蹬,賦值到底部的編輯框朝聋,然后在編輯控件內(nèi)判斷有無該字符串,有就刪除罩润,無則添加
- 當(dāng)編輯框內(nèi)玖翅,只要最后一個(gè)字符串為“@”翼馆,就顯示
用戶tableView
:利用通知監(jiān)聽UITextViewTextDidChangeNotification
然后回調(diào)傳遞最后一個(gè)字符串到控制器割以,控制器判斷顯示
四、我
(因?yàn)槲沂裁磾?shù)據(jù)也沒有应媚,就沒做那些詳細(xì)界面了)
1. 個(gè)人資料
2. 收貨地址
思路
本地?cái)?shù)據(jù)工具類,修改內(nèi)容閉包回調(diào)控制器更改數(shù)據(jù),內(nèi)部處理好邏輯關(guān)系就可以了
五延届、動(dòng)畫
① 圖片展示
思路
界面是一個(gè)UIViewController薯演,提供接口接收數(shù)據(jù),view中添加一個(gè)圖片輪播器丢胚,present出現(xiàn)后執(zhí)行動(dòng)畫(這里我只是實(shí)現(xiàn)了效果翩瓜,詳細(xì)控件分布就不做那么仔細(xì)了)
大概步驟:
- 點(diǎn)擊圖片
- 閉包回調(diào)傳遞
圖片在當(dāng)前窗口的frame值
兔跌、圖片所在數(shù)組的下標(biāo)
給控制器 - 控制器將數(shù)值傳遞給
圖片展示控制器
峡蟋,并present -
圖片展示控制器
接收圖片數(shù)據(jù)
賦值給圖片輪播器坟桅,然后創(chuàng)建一個(gè)imageView(作動(dòng)畫用)蕊蝗,frame
設(shè)置為從上一個(gè)界面接收到的數(shù)值,然后imageView執(zhí)行形變動(dòng)畫 - 動(dòng)畫執(zhí)行完畢移除imageView蓬戚,顯示圖片輪播器
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// 關(guān)閉按鈕
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];
// 設(shè)置屬性
showView.type = XCFShowViewTypeDetail;
showView.imageArray = self.imageArray;
showView.currentIndex = self.imageIndex;
showView.imageViewDidScrolledBlock = self.imageViewDidScrolledBlock;
// 默認(rèn)先隱藏
showView.hidden = YES;
[self.view addSubview:showView];
// 臨時(shí)添加一個(gè)imageView 作動(dòng)畫
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];
// 動(dòng)畫
[UIView animateWithDuration:0.3 animations:^{
imageView.frame = displayRect;
} completion:^(BOOL finished) {
// 移除動(dòng)畫的imageView
[imageView removeFromSuperview];
// 顯示圖片輪播器
showView.hidden = NO;
[UIView animateWithDuration:0.3 animations:^{
dismissButton.alpha = 1;
}];
}];
}
② 添加商品到購物車
布局
- 可以通過添加一個(gè)
window
,來實(shí)現(xiàn)這個(gè)需求豫喧,也可以用直接在根窗口[UIApplication sharedApplication].keyWindow
上添加一個(gè)自定義的UIView嘿棘,我這里用的是后者。 - 自定義一個(gè)半透明的UIView焦人,按照官方的動(dòng)畫效果重父,我這里用一個(gè)tableView房午,只設(shè)置了tableHeaderView(因?yàn)閼兴詻]有使用UIScrollView),tableHeaderView的內(nèi)容又是一個(gè)自定義UIView袋倔,控件擺放不多說了很簡單宾娜,不過需要注意的是:
- 控件之間的邏輯關(guān)系
-
商品種類button
的標(biāo)題由服務(wù)器數(shù)據(jù)決定前塔,所以整個(gè)tableView的frame并不是固定的 - 因?yàn)闆]找到可以直接在計(jì)數(shù)器中間添加一個(gè)view的接口方法承冰,所以我自定義了一個(gè)計(jì)數(shù)器
XCFStepper
困乒,很簡單的一個(gè)小控件,處理好邏輯關(guān)系就可以了
思路
- 在購物車本地?cái)?shù)據(jù)工具類
XCFCartItemTool
的刷新數(shù)據(jù)
方法中(不管是刪除還是新增凑保,都會(huì)刷新數(shù)據(jù))發(fā)送自定義的通知涌攻,并傳遞商品數(shù)量totalNumber
// 添加完成后發(fā)送通知恳谎,用處:購物車圖標(biāo)動(dòng)畫
[[NSNotificationCenter defaultCenter] postNotificationName:XCFCartItemTotalNumberDidChangedNotification
object:nil
userInfo:@{@"goodsCount" : @([self totalNumber])}];
- 購物車圖標(biāo)為自定義的UIView,
[[UIBarButtonItem alloc] initWithCustomView:cartIcon];
然后添加到導(dǎo)航欄婚苹,在view內(nèi)部接收XCFCartItemTotalNumberDidChangedNotification
通知后作核心動(dòng)畫就能實(shí)現(xiàn)效果了
- (void)awakeFromNib {
// 監(jiān)聽“添加商品到購物車”的通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cartItemTotalNumberDidChanged:)
name:XCFCartItemTotalNumberDidChangedNotification
object:nil];
NSUInteger count = [XCFCartItemTool totalNumber];
if (count) { // 有商品才顯示數(shù)量標(biāo)簽
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;
// 取得商品數(shù)量
NSUInteger count = [dict[@"goodsCount"] integerValue];
// 延時(shí)作動(dòng)畫
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.5 animations:^{
// 購物車圖標(biāo)執(zhí)行放大動(dòng)畫
self.countButton.transform = CGAffineTransformMakeScale(1.5, 1.5);
} completion:^(BOOL finished) {
// 改變顯示的商品數(shù)
[self.countButton setTitle:[NSString stringWithFormat:@"%zd", count]
forState:UIControlStateNormal];
// 還原圖標(biāo)大小
[UIView animateWithDuration:0.5 animations:^{
self.countButton.transform = CGAffineTransformMakeScale(1, 1);
}];
}];
});
}
- 這里我模擬了一下添加效果:從本地?cái)?shù)據(jù)中隨機(jī)取出一個(gè)商品
- 如果該商品只有一個(gè)類型怎炊,直接添加到購物車
- 如果該商品有多種類型(如:300g评肆、450g非区、600g)征绸,就以
碰撞方式
的動(dòng)畫(我用的是Facebook
的pop
框架)彈出商品類別選擇tableView
,同時(shí)后面的商品信息view
進(jìn)行縮放淆衷,設(shè)置要購買的類別和數(shù)量后吭敢,商品圖片執(zhí)行核心動(dòng)畫,然后移除類別選擇view
辕宏,執(zhí)行添加到購物車
動(dòng)畫 - 這個(gè)界面也比較簡單砾莱,只是要注意各種小細(xì)節(jié)腊瑟,動(dòng)畫的順序闰非,以及相應(yīng)的
添加到購物車(或立即購買)業(yè)務(wù)邏輯
// 隨機(jī)添加一樣商品
XCFCartItem *randomItem = [XCFCartItemTool randomItem];
XCFGoods *randomGoods = randomItem.goods;
// 加入購物車
if (type == BottomViewClickedAddToShoppingCart) {
// 如果該商品有多種類型财松,就彈窗讓用戶選擇具體購買哪種類型
if (randomGoods.kinds.count > 1) {
UIWindow *window = [UIApplication sharedApplication].keyWindow;
// 縮小當(dāng)前界面
[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];
// 確認(rèn)購買回調(diào)
kindsView.confirmBlock = ^(XCFCartItem *item) {
// 本地購物車數(shù)據(jù)添加商品
[XCFCartItemTool addItem:item];
[UILabel showStats:[NSString stringWithFormat:@"添加:\n%@", item.kind_name] atView:weakSelf.view];
};
// 取消回調(diào)
kindsView.cancelBlock = ^{
// 恢復(fù)界面大小
[UIView animateWithDuration:0.3 animations:^{
window.rootViewController.view.transform = CGAffineTransformMakeScale(1, 1);
}];
};
} else { // 如果只有一個(gè)商品,直接加入購物車
[XCFCartItemTool addItemRandomly:^(NSString *goodsName) {
[UILabel showStats:[NSString stringWithFormat:@"隨機(jī)添加:\n%@", goodsName] atView:weakSelf.view];
}];
}
}
最后想說的話
- 關(guān)于項(xiàng)目
- 自動(dòng)布局是按照iPhone6s的甜害,其他機(jī)型可能會(huì)有偏差尔店,見諒
- 能實(shí)現(xiàn)的基本都實(shí)現(xiàn)了
-
市集
模塊接口被加密闹获,所以做不了河哑,不過目測是簡單的CollectionView+一些動(dòng)畫就能搞定的 -
菜籃子
界面應(yīng)該是我的實(shí)現(xiàn)思路有問題所以沒完成璃谨,后續(xù)如果有空的話會(huì)嘗試實(shí)現(xiàn) - 一些很簡單的佳吞、相同效果的界面我
實(shí)在不想寫了底扳!也沒做,有興趣的童鞋可以自己嘗試實(shí)現(xiàn)
-
- 寫代碼過程中跑去騷擾了ManoBoo小神鹊汛,在這里超級(jí)感謝ManoBoo刁憋!還幫我解決了兩個(gè)bug至耻!還有感謝維尼的小熊大神的開源App尘颓,兩位前輩對(duì)界面實(shí)現(xiàn)思路的講解讓我受益匪淺晦譬。感謝一切開源蛔添。
Github代碼下載地址
高仿下廚房App 開源咯~
-
如果你是新手,并且我的項(xiàng)目對(duì)你有幫助的話逸吵,請(qǐng)大方的給我Star扫皱!開源的世界如此美好互幫互助感激不盡捷绑!如果你不給我Star粹污,也不強(qiáng)求啦...
-
如果你是大神...