前言
本開源項目講解了一些App常見功能界面的搭建以及實現思路,適合新手。
為什麼是下廚房?
下廚房:一個集合了工具、社區與平台電商屬性的家庭美食入口。很棒的一個平台,App界面也很好看!
關於項目(Github地址在文章結尾)
開發環境:Xcode 7.2,語言:Objective-C
用到的工具:Charles抓包工具
仿寫程度:耗時一個月,每天12h+,能實現的基本都實現了
剛開始寫的時候控件是用純代碼寫的,後來發現太耗時間就改用Xib了
因為此前沒看過誰的代碼(ManoBoo跟維尼的小熊兩位前輩的都是swift,我還沒學swift就沒看了),所以代碼如果有不合理的地方(如:命名),請見諒
這個開源項目適合新手,基本的界面布局以及業務邏輯都有,看完這個基本也會寫簡單的App啦,但是因為我也是新手,代碼很多不規范,所以學習實現思路就好,代碼請不要借鑒。
非科班自學,請大神勿噴。
效果預覽
一、首頁
布局
如圖,首頁tableView就可以搞定
日期標題為sectionHeader
下面的就是cell了,下廚房返回的數據中cell有6種模板,通過自定義cell,根據不同模板顯示不同效果,很簡單就不描述了
思路:
頂部導航部分點擊事件,通過給每個控件綁定tag,然後定義對應的枚舉變量,通過閉包(Block)將事件傳遞到控制器後,控制器判斷枚舉值即可。
1. 跳轉的界面控制器
① 菜譜
布局
整個界面是一個UIViewController,放上一個tableView,然後添加底部的收藏、丟進菜籃子自定義view
用料、做法、小貼士、被加入的菜單這四個標題是sectionHeader,其內容對應為一組,每組cell自定義即可
作品展示:
這裡實現的方法跟上面的用料、做法...一樣,獨立為一組,組內只有一個cell,cell的contentView裡從上至下添加:作品個數Label、作品展示CollectionView、所有作品Button即可
collectionView手勢左滑,松開會加載更多作品數據。這裡通過實現scrollView的代理方法,判斷contentOffset的值是否達到預定的數值,達到即調用block,然後控制器發送網絡請求加載更多數據,刷新界面即可。(這裡我只是實現了一個需求,並沒有進一步優化調整)
布局:
如圖即可,底部加入菜單button也可以是sectionFooter,
雖然下廚房幾乎沒有邊框(有分割線),但仔細分析還是很好劃分的
② 作品
布局
因為關注動態、買買買界面跟這個差不多,需要復用到這個界面的內容,所以這整個界面是只有一個cell的tableView
上面是個圖片輪播器,下面添加控件即可,描述Label以及用戶評論Label的高度是有內容決定的,這個只需要在模型中添加一個labelHeight屬性,然後在內部計算好高度,直接返回給控件就可以了。控件部分可以根據不同界面顯示的不同效果,分割成若干部分,然後給cell添加一個type屬性(枚舉類型),創建的時候告訴cell屬於哪一種type,然後根據不同type進行調整即可。
2. 導航
① 關注動態
布局
整個界面就一個tableView,一個動態為作品界面的cell
遇到的問題
圖片輪播器會受tableViewCell的復用機制影響,導致錯亂(點贊按鈕的狀態是由服務器返回的數據決定的,這裡我就不模擬了)
解決辦法:
在控制器中添加一個記錄圖片輪播器滾動位置的數組屬性imageViewCurrentLocationArray
在圖片輪播器裡實現scrollView代理方法,監聽記錄最終的位移contentOffset.x,停止滾動後通過閉包/代理將位置數據傳遞到控制器,控制器將位置數據添加到記錄數組中即可,此方法應該同樣適用其他因cell復用機制導致的數據顯示混亂問題,(關於數組的操作,應考慮到:下拉刷新,上拉加載更多數據以及其他情況,詳細代碼見工程)
至於實現哪個代理方法最為合理,應該視實際的業務需求以及界面效果而定,下面的textField代理也是如此
// scrollView停止滾動後記錄contentOffset.x - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { !self.imageViewDidScrolledBlock ? : self.imageViewDidScrolledBlock(scrollView.contentOffset.x); }
最後圖片輪播器添加一個屬性接口,接收位置數據,然後在構造方法裡設置圖片輪播器的contentOffset即可
- (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]; } }
② 三餐
布局
如圖,整個控制器是ViewController,將CollectionView以及上傳button添加到viewController.view即可,比較簡單
導航欄的標題是自定義的view,然後self.navigationItem.titleView = view;即可
這個界面的接口號稱“時時死”,如果想看效果的童鞋可以自己重新抓包
3. 功能界面
① 菜譜草稿(整個項目最難的界面)
菜譜創建 - 上半部分.png
菜譜創建 - 下半部分.png
布局
如圖所示即可,需要注意的是:因為這個界面是操作本地數據,所以要時刻根據數據得變化判斷控件是否顯示、如何顯示
思路
因為是創建以及草稿功能的界面,所以操作的是本地數據,我寫了一個菜譜草稿數據工具類,用來增刪改查,非常方便
照片上傳:
點擊彈出ActionSheet讓用戶選擇是相機、還是相冊,然後通過UIImagePickerController的代理方法- imagePickerController:didFinishPickingMediaWithInfo:選取照片即可
因為頂部跟做法都有上傳圖片的需求,如果不做判斷是誰需要設置圖片,會導致數據顯示錯亂,這個在代理方法中通過判斷代理的調用者即可解決圖片錯亂
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary*)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]; }]; }
做法步驟:
點擊添加步驟,往tableView對應位置插入一行步驟cell,並且在模型數據數組對應位置插入一個數據為空的做法數據,如果不添加的話,點擊編輯就會因為數據越界導致崩潰
// 增加一行點擊回調 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]; };
點擊調整步驟,執行回調後tableView進入對應狀態,[self.tableView setEditing:YES animated:YES];,這裡需要注意的問題有:
tableView中只有做法這一組cell才進入編輯模式,在代理方法-tableView:canMoveRowAtIndexPath:中判斷即可
需要設置cell目標移動的位置,即使其他組不能進入編輯模式,但做法步驟cell還是能移動插入其中,所以需要設置:如果超過了做法步驟所在的section,不管怎麼移動,最終都回到做法步驟section
調整移動了cell之後,也需要同步數據中對應做法步驟的位置
// 如果不是步驟數組,就回到對應位置 - (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; }
用料:
原理大致與做法步驟相同,只是用料的編輯是在一個新的控制器XCFIngredientEditController,不管有無用料,點擊都進入這個控制器,那麼:
在XCFIngredientEditController中添加一個接口,接收已存在的用料數據,如果數據為空,就默認添加兩個示例cell,如果不為空,就顯示已存在的數據,從而達到新增、編輯的效果
編輯完成或保存後,pop回創建菜譜控制器,並執行回調將編輯好的用料數據回傳,刷新界面即可
因為官方的效果是:只要操作了數據,即使不點擊保存,也會將數據保存起來,所以我在每個數據操作後面都更新了本地數據[self updateDarft];
② 搜索
布局
進入搜索控制器時,判斷本地數據中是否有已經搜索過的關鍵詞,有則加載,沒有就不顯示第0組cell
輸入文字時,利用通知監聽UITextFieldTextDidChangedNotification,然後通過閉包回傳textField的文字內容給控制器,同時時刻刷新tableView即可
底部流行搜索關鍵詞是網絡數據加載的,一個九宮格搞定
思路
因為沒有接口,所以我封裝了一個本地單例類,保存搜索過的關鍵詞,並提供數據操作的方法
搜索:在本地數據中遍歷是否已存在該關鍵詞,存在就將舊關鍵詞刪除,然後穿插新關鍵詞到第0位;如果不存在就直接插入到第0位即可(詳細代碼見工程)
③ 上傳作品
布局
一個只有tableHeaderView的tableViewController搞定,官方App中圖片、標簽的添加會有動畫,我這裡沒有實現,大概就是在改變控件frame值時添加動畫即可
思路
本地工具類,並不需要進行數據持久化,通過回調就可以完成數據操作
需要注意的是:處理好圖片、標簽長度的顯示以及換行
二、市集
1. 商品
布局
上面這部分我的實現方法是:全部作為一個tableHeaderView,然後內部細分為3個部分,控制好布局就可以了
需要注意的是:商品優惠(紅色邊框button)、店鋪優惠(橙色),服務器返回的是字符串類型數據,這裡我將優惠信息以button展示(也可以Label),因為數據是動態的,所以可以通過計算字符串最大寬度,然後加上既定的長度,即可設置每個button的不同大小
商品界面.gif
思路
整個控制器是UIViewController,上面商品信息展示部分是tableView,下面圖文詳情界面是一個UIView(UIView上面放一個類似導航的標簽view,下面為CollectionView,CollectionView的cell內部添加tableView),設置好對應frame即可
繼續拖動,查看圖文詳情:
這個通過實現scrollView代理方法,判斷contentOffset.y的值是否達到預定值,官方效果是:不必等到松手,達到即商品展示tableView和圖文詳情view同時進行位移動畫,同時商品展示tableView.hidden = YES;,從而達到動畫切換界面的效果。由圖文詳情切換回商品展示也是通過代理實現
// 向上拖動到一定程度,切換至圖文詳情界面 - (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]; }]; } }
評價CollectionView手勢左滑切換控制器:
也是實現scrollView代理方法,通過回調切換控制器
曬圖/全部評價界面:
官方效果是:兩個tableView重疊在一起,點擊導航欄對應按鈕設置tableView.hidden
2. 購物車
布局
整個控制器是UIViewController,self.view添加tableView跟底部的結算view
同一個店鋪的商品作為一組cell,店鋪名為sectionHeader
思路(我設置了清空購物車就重新加載本地數據)
官方是通過服務器接收數據顯示購物車內容的,因為沒接口所以我就新建了一個本地單例類,保存購物車的數據,並提供數據操作方法
業務邏輯:
給每個商品添加一個XCFCartItemState枚舉類型屬性,記錄是否被選中,點選商品勾選button後通過回調,在控制器更改本地數據。
店鋪勾選狀態以及全選狀態,通過遍歷本地數據中對應店鋪內商品或所有商品是否全部選中,再通過點選商品回調刷新界面就可以達到實時更改狀態的效果了
商品購買數量:
在cell內部實現textField代理方法-textFieldShouldEndEditing:監聽購買數量,編輯完成就通過閉包傳遞購買數量給控制器,控制器更改本地數據並刷新界面
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. 確認訂單
布局
如圖,需要注意的是:如果計算結果店鋪優惠價格為0,就會隱藏店鋪優惠
思路
直接拿到購物車界面勾選好的的商品,通過計算顯示對應價格數字就可以了(運費只添加一次)
選擇收貨地址,訪問本地存儲的收貨地址數據,有則顯示,沒有則提示添加
因為沒錢沒見過訂單、優惠長啥樣,所以就沒做了,不過應該就是tableView就可以搞定的
三、社區
思路
點選一個評論cell,就獲取該評論作者的昵稱,賦值到底部的編輯框,然後在編輯控件內判斷有無該字符串,有就刪除,無則添加
當編輯框內,只要最後一個字符串為“@”,就顯示用戶tableView:利用通知監聽UITextViewTextDidChangeNotification然後回調傳遞最後一個字符串到控制器,控制器判斷顯示
四、我
(因為我什麼數據也沒有,就沒做那些詳細界面了)
1. 個人資料
2. 收貨地址
思路
本地數據工具類,修改內容閉包回調控制器更改數據,內部處理好邏輯關系就可以了
五、動畫
① 圖片展示
思路
界面是一個UIViewController,提供接口接收數據,view中添加一個圖片輪播器,present出現後執行動畫(這裡我只是實現了效果,詳細控件分布就不做那麼仔細了)
大概步驟:
點擊圖片
閉包回調傳遞 圖片在當前窗口的frame值、圖片所在數組的下標給控制器
控制器將數值傳遞給圖片展示控制器,並present
圖片展示控制器接收圖片數據賦值給圖片輪播器,然後創建一個imageView(作動畫用),frame設置為從上一個界面接收到的數值,然後imageView執行形變動畫
動畫執行完畢移除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; }]; }]; }
② 添加商品到購物車
布局
可以通過添加一個window,來實現這個需求,也可以用直接在根窗口[UIApplication sharedApplication].keyWindow上添加一個自定義的UIView,我這裡用的是後者。
自定義一個半透明的UIView,按照官方的動畫效果,我這裡用一個tableView,只設置了tableHeaderView(因為懶所以沒有使用UIScrollView),tableHeaderView的內容又是一個自定義UIView,控件擺放不多說了很簡單,不過需要注意的是:
控件之間的邏輯關系
商品種類button的標題由服務器數據決定,所以整個tableView的frame並不是固定的
因為沒找到可以直接在計數器中間添加一個view的接口方法,所以我自定義了一個計數器XCFStepper,很簡單的一個小控件,處理好邏輯關系就可以了
思路
在購物車本地數據工具類XCFCartItemTool的刷新數據方法中(不管是刪除還是新增,都會刷新數據)發送自定義的通知,並傳遞商品數量totalNumber
// 添加完成後發送通知,用處:購物車圖標動畫 [[NSNotificationCenter defaultCenter] postNotificationName:XCFCartItemTotalNumberDidChangedNotification object:nil userInfo:@{@"goodsCount" : @([self totalNumber])}];
購物車圖標為自定義的UIView,[[UIBarButtonItem alloc] initWithCustomView:cartIcon];然後添加到導航欄,在view內部接收XCFCartItemTotalNumberDidChangedNotification通知後作核心動畫就能實現效果了
- (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); }]; }]; }); }
這裡我模擬了一下添加效果:從本地數據中隨機取出一個商品
如果該商品只有一個類型,直接添加到購物車
如果該商品有多種類型(如:300g、450g、600g),就以碰撞方式的動畫(我用的是Facebook的pop框架)彈出商品類別選擇tableView,同時後面的商品信息view進行縮放,設置要購買的類別和數量後,商品圖片執行核心動畫,然後移除類別選擇view,執行添加到購物車動畫
這個界面也比較簡單,只是要注意各種小細節,動畫的順序,以及相應的添加到購物車(或立即購買)業務邏輯
// 隨機添加一樣商品 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]; }]; } }
最後想說的話
關於項目
自動布局是按照iPhone6s的,其他機型可能會有偏差,見諒
自學半年,這是我的第一個項目,bug在所難免,請見諒
我是用虛擬機開發的...所以沒能真機調試...(無Mac也不能黑蘋果)
能實現的基本都實現了
市集模塊接口被加密,所以做不了,不過目測是簡單的CollectionView+一些動畫就能搞定的
菜籃子界面應該是我的實現思路有問題所以沒完成,後續如果有空的話會嘗試實現
一些很簡單的、相同效果的界面我實在不想寫了!也沒做,有興趣的童鞋可以自己嘗試實現
這是一個新手的代碼,有些設計如果不合理的話,歡迎指正!至於命名,實在是一門學問啊...接下來得好好規范一下了
寫代碼過程中跑去騷擾了ManoBoo小神,在這裡超級感謝ManoBoo!還幫我解決了兩個bug!還有感謝維尼的小熊大神的開源App,兩位前輩對界面實現思路的講解讓我受益匪淺。感謝一切開源。
關於學習
從一個閉包都不會用的新手,到能實現一些基本界面效果、業務需求,感觸良多:
直接做伸手黨也許也能實現需求,但獨立思考更有助於成長
動手敲代碼之前,應考慮全面(如:哪些控件會被循環使用以及如何使用,根據不同情況接口如何設計才更合理,等等)
代碼不夠美觀,命名簡直不規范,接下來需要花時間好好學習一下
對諸如數組、字典的數據操作應格外小心。出現問題/bug(比如新手常見的數組越界),用Cyan大神的話說就是
永遠應該找出導致問題的原因並且解決它,而不是加保護去避免問題
連續敲了一個月沒有假期,從一開始整理接口模型數據,到後來吃飯睡覺拉粑粑都在想怎麼實現需求效果,感覺時間過得很快,技能點get到很多,但仍遠遠不夠
接下來:
聽取ManoBoo小神的意見滾去蘋果開發者中心好好學一下代碼規范
學習swift
學習實用技能,內存管理,性能優化,嘗試搭建復雜的界面
學習編程思想
Github代碼下載地址
高仿下廚房App
備注:
因為接口問題,以及發現項目被一些人拿去造假簡歷給用人單位造成了不好的影響所以清空了項目
新手借鑒一下實現思路就足夠了,代碼都不難的,請見諒啦!
文/callmeJoeJoe(簡書作者)
原文鏈接:http://www.jianshu.com/p/a8f619a2c622