iOS高仿下廚房(Objective-C)

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ù)覽

首頁.gif
關(guān)注動(dòng)態(tài).gif
菜譜.gif
帖子與作品.gif
商品界面.gif
商品分類選擇.gif
商品界面-圖片展示動(dòng)畫.gif
購物車.gif
上傳作品.gif
收貨地址.gif
搜索界面.gif
菜譜-創(chuàng)建與刪除.gif

一、首頁

首頁.png
布局
  • 如圖蛤育,首頁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自定義即可
菜譜 - Header.png
菜譜 - 作品展示.png
  • 作品展示:
  • 這里實(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)整)
菜譜 - 底部.png
  • 布局:
    如圖即可猫态,底部加入菜單button也可以是sectionFooter,
    雖然下廚房幾乎沒有邊框(有分割線)披摄,但仔細(xì)分析還是很好劃分的

② 作品

作品.png
布局
  • 因?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)

關(guān)注動(dòng)態(tài).gif
布局
  • 整個(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];
    }
}

② 三餐

作品.png
布局
  • 如圖嫁佳,整個(gè)控制器是ViewController挨队,將CollectionView以及上傳button添加到viewController.view即可,比較簡單

  • 導(dǎo)航欄的標(biāo)題是自定義的view蒿往,然后self.navigationItem.titleView = view;即可

這個(gè)界面的接口號(hào)稱“時(shí)時(shí)死”盛垦,如果想看效果的童鞋可以自己重新抓包


3. 功能界面

① 菜譜草稿(整個(gè)項(xiàng)目最難的界面)

菜譜創(chuàng)建 - 上半部分.png
菜譜創(chuàng)建 - 下半部分.png
布局

如圖所示即可,需要注意的是:因?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];
    }];
}
草稿界面-做法.gif
  • 做法步驟
  • 點(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;
}
草稿界面-用料.gif
  • 用料:
    原理大致與做法步驟相同,只是用料的編輯是在一個(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];


② 搜索

搜索界面.gif
搜索界面.png
布局
  • 進(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ì)代碼見工程)

③ 上傳作品

上傳作品.gif
布局

一個(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. 商品

商品界面.png
布局
  • 上面這部分我的實(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的不同大小
商品界面.gif
商品界面-評(píng)價(jià).gif
思路
  • 整個(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. 購物車

購物車.gif
布局
  • 整個(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)訂單

訂單 - 上半部分.png
訂單 - 下半部分.png
布局

如圖,需要注意的是:如果計(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ū)

社區(qū).gif
思路
  • 點(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è)人資料

個(gè)人界面.gif

2. 收貨地址

收貨地址.gif
思路

本地?cái)?shù)據(jù)工具類,修改內(nèi)容閉包回調(diào)控制器更改數(shù)據(jù),內(nèi)部處理好邏輯關(guān)系就可以了


五延届、動(dòng)畫

① 圖片展示

商品界面-圖片展示動(dòng)畫.gif
思路

界面是一個(gè)UIViewController薯演,提供接口接收數(shù)據(jù),view中添加一個(gè)圖片輪播器丢胚,present出現(xiàn)后執(zhí)行動(dòng)畫(這里我只是實(shí)現(xiàn)了效果翩瓜,詳細(xì)控件分布就不做那么仔細(xì)了)
大概步驟:

  1. 點(diǎn)擊圖片
  2. 閉包回調(diào)傳遞 圖片在當(dāng)前窗口的frame值兔跌、圖片所在數(shù)組的下標(biāo)給控制器
  3. 控制器將數(shù)值傳遞給圖片展示控制器峡蟋,并present
  4. 圖片展示控制器接收圖片數(shù)據(jù)賦值給圖片輪播器坟桅,然后創(chuàng)建一個(gè)imageView(作動(dòng)畫用)蕊蝗,frame設(shè)置為從上一個(gè)界面接收到的數(shù)值,然后imageView執(zhí)行形變動(dòng)畫
  5. 動(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;
        }];
    }];
}

② 添加商品到購物車

商品分類選擇.gif
商品分類view.png
布局
  • 可以通過添加一個(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)畫(我用的是Facebookpop框架)彈出商品類別選擇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)求啦...

  • 如果你是大神...

END

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末进苍,一起剝皮案震驚了整個(gè)濱河市觉啊,隨后出現(xiàn)的幾起案子沈贝,更是在濱河造成了極大的恐慌宋下,老刑警劉巖杨凑,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撩满,死亡現(xiàn)場離奇詭異伺帘,居然都是意外死亡伪嫁,警方通過查閱死者的電腦和手機(jī)偶垮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門脚猾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人砰奕,你說我怎么就攤上這事军援⌒馗纾” “怎么了空厌?”我有些...
    開封第一講書人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵蝇庭,是天一觀的道長捡硅。 經(jīng)常有香客問我壮韭,道長喷屋,這世上最難降的妖魔是什么屯曹? 我笑而不...
    開封第一講書人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任恶耽,我火速辦了婚禮偷俭,結(jié)果婚禮上涌萤,老公的妹妹穿的比我還像新娘口猜。我一直安慰自己济炎,他們只是感情好冻辩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開白布恨闪。 她就那樣靜靜地躺著咙咽,像睡著了一般钧敞。 火紅的嫁衣襯著肌膚如雪溉苛。 梳的紋絲不亂的頭發(fā)上弄诲,一...
    開封第一講書人閱讀 48,954評(píng)論 1 283
  • 那天寂玲,我揣著相機(jī)與錄音梗摇,去河邊找鬼伶授。 笑死糜烹,一個(gè)胖子當(dāng)著我的面吹牛景图,可吹牛的內(nèi)容都是我干的挚币。 我是一名探鬼主播,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼趁怔!你這毒婦竟也來了润努?” 一聲冷哼從身側(cè)響起示括,我...
    開封第一講書人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤鳍侣,失蹤者是張志新(化名)和其女友劉穎吼拥,沒想到半個(gè)月后凿可,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體矿酵,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡全肮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年休建,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了测砂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片百匆。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡存璃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出粘招,到底是詐尸還是另有隱情洒扎,我是刑警寧澤袍冷,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布难裆,位于F島的核電站乃戈,受9級(jí)特大地震影響症虑,放射性物質(zhì)發(fā)生泄漏谍憔。R本人自食惡果不足惜主籍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一苫昌、第九天 我趴在偏房一處隱蔽的房頂上張望祟身。 院中可真熱鬧物独,春花似錦挡篓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杏瞻。三九已至捞挥,卻和暖如春砌函,著一層夾襖步出監(jiān)牢的瞬間讹俊,已是汗流浹背仍劈。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來泰國打工贩疙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留这溅,地道東北人悲靴。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓对竣,卻偏偏與公主長得像否纬,于是被迫代替她去往敵國和親临燃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子膜廊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容