前言
很早前就想和大家分享一些真正項目的開發思路和流程,一直沒能鼓起勇氣寫,畢竟這不是一件輕松的事情,一個月前,公司的項目上線,鼓起勇氣利用每天的休息時間寫了這個半成品的項目,基本的UI與邏輯都打通了,剩下的細節需要時間修改,由於下周工作有新需求需要開發,可能很長一段時間沒精力來寫了,本想把所有的功能都實現,並且修改掉發現的bug後再給大家學習的,由於工作的情況,可能會延緩2個月左右的日子(後續我會把功能陸續實現),代碼我傳到github上了,相信讀者會發現很多的不足之處,希望大家可以自行嘗試修改一些bug和實現剩余的部分功能,說實話一直是每天晚上8點寫到凌晨3點左右,因為有些朋友不希望看見過多的三方庫,所以基本非常耗時的需求我都盡力自己封裝的,當然我寫的有很多不足之處,希望大家可以指出不足之處,共同進步,由於時間比較匆忙,我的code一次review都沒,望大家包涵,項目使用OC寫的,算是對OC的一個紀念吧。等忙完這段時間,我會再用swift來寫一個新的項目分享給大家,喜歡的朋友可以繼續關注我的博客,我會第一時間將新項目到博客上
這篇博客是配合代碼來寫的,大部分的圖片都是我自己摳下來的,數據也是我直接寫的假數據(嘗試抓了下接口都是加密的- -)在代碼中基本每一步我都有詳細的注釋,唠叨的有點多,下面就先展示下我仿的這個項目吧,有興趣的朋友可以下一下原版的app參照一下(寫的過程中app更新了一點新功能)
項目展示,由於沒有數據,所以所有的cell顯示的都是我自己寫的數據。
抽屜
首頁部分效果
首頁效果
部分效果
發現
消息
搜索
設置
模糊效果
代碼注釋展示
代碼注釋展示
還有很多細節就不一一展示了,大家將代碼運行下自己查看即可。由於內容比較多,我就按功能模塊來介紹給大家了。
首先是左邊抽屜的效果以及點擊按鈕切換控制器
實際這裡相當於自己定義一個和系統UITabBarController差不多功能的控件,在最底層有一個控制器(後面稱之為主控制器),將左邊的按鈕view添加到主控制器的view上,創建好右邊有所的控制器(首頁,發現,消息,設置...)並且將每個右邊控制器包裝一個導航控制器,將導航控制器按序添加給主控制器做子控制器,默認情況下將首頁的導航控制器的view添加給主控制器的view子控件,根據左邊按鈕的點擊事件通過代理方法.移除舊控制器view從父視圖,將新的view添加到主視圖的view具體代碼如下,用一個臨時屬性之前選中的控制器
//暫時先做沒有登陸的情況的點擊 WNXNavigationController *newNC = self.childViewControllers[toIndex]; if (toIndex == WNXleftButtonTypeIcon) { newNC = self.childViewControllers[fromIndex]; } //移除舊的控制器view WNXNavigationController *oldNC = self.childViewControllers[fromIndex]; [oldNC.view removeFromSuperview]; //添加新的控制器view [self.view addSubview:newNC.view]; newNC.view.transform = oldNC.view.transform; self.showViewController = newNC.childViewControllers[0];
這樣就完成了切換控制器
抽屜的效果是通過給控制器view做形變動畫完成的,由於每個導航控制器的功能一樣,這裡抽取了共同的特點封裝了一個基類導航控制器,點擊左邊的按鈕完成抽屜效果
拖動手勢是給主控制器添加一個UIPanGestureRecognizer手勢,根據拖動的距離計算出該停留在哪裡的位置,這裡判斷很多,具體實現我在代碼中每一步都有注釋,參照代碼即可
首頁
首頁就是一個tableView就可以搞定,tableView的headView顏色和數據服務器會給返回,給每個headView添加一個點擊手勢,點擊push到下一個控制器,導航條的顏色會和前一個headView的顏色一樣,,這裡由於我之前設置了導航控制器的主題
[UINavigationBar appearanceWhenContainedIn:self, nil]
所以不可以直接設置導航條的顏色了 這裡我嘗試了設置navigationBar的背景色,設置navigationBar的setTintColor:
設置navigationBar.layer的背景色 以及根據顏色畫出navigationBar的背景圖片4種辦法都無法達到原生的效果
最後采用將navigationBar隱藏,自己放一個View了冒充導航條來解決這個問題
發現
這個頁面是一個UICollectionView,裡面有兩組數據,每一組都一個一個headView,需要注意的就是cell的點擊事件,這裡注意了下官方的做法是不論點擊了cell的哪個位置,都會使cell內部的button進入高亮狀態,這裡需要用到事件的響應鏈,在cell的內部攔截整個cell的點擊事件都交給按鈕來做,具體代碼如下
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { /* 攔截事件響應者,不論觸發了cell中的哪個控件都交給iconButton來響應 */ // 1.判斷當前控件能否接收事件 if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil; // 2. 判斷點在不在當前控件 if ([self pointInside:point withEvent:event] == NO) return nil; return self.iconButton; }
但這裡需要注意這樣攔截cell的點擊事件,在collectionView的cell被點擊觸發didDeselectItemAtIndexPath:就不會被觸發了,我的解決方法是在點擊button時通過代理方法傳給collectionView,外部在通過知道點擊了那個cell,push到下一個控制器,並將cell的model賦值給下一個控制器
登陸(登陸只用了微信和新浪登陸,不涉及到注冊就非常簡單,這些只需去官網下來登陸和分享的sdk集成進來即可,我一般使用友盟平台,包括崩潰統計,三方登陸,分享,用戶分析等等)
消息
一樣這裡也是tabelView,這裡我個人的邏輯是將所有的消息歸檔到本地,每次點擊刪除一條,將本地的數據刪除一條,重新歸檔
當點擊刪除全部的時候,就清空本地的歸檔數據,下次接受的服務器的數據在重新寫入
因為是模擬的數據,為了保障每次進來都有數據,就沒有實現歸檔解檔的操作,所以每次刪除後重新進入會再次有數據
這裡記錄編輯按鈕的狀態,讀取本地是否有未讀消息數組的個數,如果有就顯示編輯按鈕,記錄編輯按鈕的狀態,如果是選中狀態就隱藏>圖片,顯示刪除按鈕,點擊刪除按鈕就將本地的數據數組刪除掉並且刷新tableView,這裡用的是刪除動畫,需要注意刪除的順序
[self.datas removeObjectAtIndex:indexPath.row]; [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.tableView reloadData]; });
底部的刪除全部按鈕完全交給編輯按鈕來控制.
搜索
搜索
這個也需要持久化存儲功能,每次頁面彈出後,先從本地讀取用戶歷史搜索的數據,用戶每次刪除或者新輸入收縮內容時也直接寫入到本地的caches文件中
這裡需要提一下關於熱門按鈕的布局,因為熱門的文字長度不一樣,但每次只有4個按鈕,在xib中先將按鈕的位置約束好,不過寬度的約束需要倆個,一個是>= 和<= 然後根據服務器返回的實際長度在設置按鈕title時,計算出每個按鈕的真實寬度,根據真實寬度算出間距是多少,重新布局一次按鈕的位置
(void)setHotDatas:(NSMutableArray *)hotDatas { _hotDatas = hotDatas; //判斷是長度是否是4,開發中可以這樣寫 應該服務器返回幾條數據就賦值多少,而不是固定的寫死數據, //萬一服務器返回的數據有錯誤,會造成用戶直接閃退的,有 //時在某些不是很重要的東西無法確定返回的是否正確,建議用 //@try @catch來處理, //即便返回的數據有誤,也可以讓用戶繼續別的操作, //而不會在無關緊要的小細節上造成閃退 if (hotDatas.count == 4) { [self.hotButton1 setTitle:hotDatas[1] forState:UIControlStateNormal]; [self.hotButton2 setTitle:hotDatas[0] forState:UIControlStateNormal]; [self.hotButton3 setTitle:hotDatas[2] forState:UIControlStateNormal]; [self.hotButton4 setTitle:hotDatas[3] forState:UIControlStateNormal]; } [self layoutIfNeeded]; //算出間距 CGFloat margin = (WNXAppWidth - 40 - self.hotButton1.bounds.size.width - self.hotButton2.bounds.size.width - self.hotButton3.bounds.size.width - self.hotButton3.bounds.size.width) / 3; //更新約束 [self.hotButton2 updateConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.hotButton1.right).offset(margin); }]; [self.hotButton3 updateConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.hotButton2.right).offset(margin); }]; [self.hotButton4 updateConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.hotButton3.right).offset(margin); }]; }
模糊效果
模糊效果
這裡由於圖片的質量被壓縮的太厲害,實際的效果還不錯,這個功能是iOS8新開放的接口,有倆個圖片特效可以使用,一個是模糊blur,一個是顏色疊加(類似於PS中圖片疊加的效果,不過就3種效果)
詳情頁
詳情頁展示
這個頁面挺坑的,需要注意的細節太多,也是我耗時最久的頁面,誠然目前bug依舊不少
這個頁面的層級關系很重要,需要重點注意
首先是導航條,這個咋一看好像是導航條有個漸隱漸現的動畫,我的做法是在頂部放了一個高度為64的view,根據tableView的偏移量計算出view的透明度,但是透明度只是1或者0,頂部的scrollView裡面裝的imageView,根據服務器返回的圖片地址個數,設置他的展示內容大小,並且在整一個scrollView最上面添加一個和導航條一樣顏色的view,用它來做出向上推出現綠色的效果,並且根據底部scrollview的偏移計算拉伸的大小,我這裡拉伸的大小不是很准確,感覺需要將錨點釘在最頂端。
然後是中間切換tableView的view(後面就叫它選擇view),要實現能像headView一樣,卡在導航條下面的效果,這裡因為沒有導航條,並且在切換tableView時候不會帶走選擇view,所以只能將他放到和頂部的view在同一個層級中,同樣根據底部scrollView的contentOffset.y計算他的位置,當偏移量超過頂部的64時,就停留在那,不超過時就回到頂部view的下面,這裡的計算我加了很多的注釋,怕計算的朋友也會看的懂的,大概是這樣
-(void)scrollViewDidScroll:(UIScrollView *)scrollView { if (scrollView == self.rmdTableView || scrollView == self.infoTableView) {//說明是tableView在滾動 //記錄當前展示的是那個tableView self.showingTableView = (UITableView *)scrollView; //記錄出上一次滑動的距離,因為是在tableView的contentInset中偏移的ScrollHeadViewHeight,所以都得加回來 CGFloat offsetY = scrollView.contentOffset.y; CGFloat seleOffsetY = offsetY - self.scrollY; self.scrollY = offsetY; //修改頂部的scrollHeadView位置 並且通知scrollHeadView內的控件也修改位置 CGRect headRect = self.topView.frame; headRect.origin.y -= seleOffsetY; self.topView.frame = headRect; //根據偏移量算出alpha的值,漸隱,當偏移量大於-180開始計算消失的值 CGFloat startF = -180; //初始的偏移量Y值為 頂部倆個控件的高度 CGFloat initY = SelectViewHeight + ScrollHeadViewHeight; //缺少的那一段漸變Y值 CGFloat lackY = initY + startF; //自定義導航條高度 CGFloat naviH = 64; //漸隱alpha值 CGFloat alphaScaleHide = 1 - (offsetY + initY- lackY) / (initY- naviH - SelectViewHeight - lackY); //漸現alph值 CGFloat alphaScaleShow = (offsetY + initY - lackY) / (initY - naviH - SelectViewHeight - lackY) ; if (alphaScaleShow >= 0.98) { //顯示導航條 [UIView animateWithDuration:0.04 animations:^{ self.naviView.alpha = 1; }]; } else { self.naviView.alpha = 0; } self.topScrollView.naviView.alpha = alphaScaleShow; self.subTitleLabel.alpha = alphaScaleHide; self.smallImageView.alpha = alphaScaleHide; /* 這段代碼很有深意啊。。最開始是直接用偏移量算的,但是回來的時候速度比較快時偏移量會偏度很大 然後就悲劇了。換了好多方法。。最後才開竅T——T,這一段我會在blog裡面詳細描述我用的各種錯誤的方法 用了KVO監聽偏移量的值,切換了selectView的父控件,切換tableview的headView。。。 */ if (offsetY >= -(naviH + SelectViewHeight)) { self.selectView.frame = CGRectMake(0, naviH, WNXAppWidth, SelectViewHeight); } else { self.selectView.frame = CGRectMake(0, CGRectGetMaxY(self.topView.frame), WNXAppWidth, SelectViewHeight); } CGFloat scaleTopView = 1 - (offsetY + SelectViewHeight + ScrollHeadViewHeight) / 100; scaleTopView = scaleTopView > 1 ? scaleTopView : 1; //算出頭部的變形 這裡的動畫不是很准確,好的動畫是一點一點試出來了 這裡可能還需要配合錨點來進行動畫,關於這種動畫我會在以後單開一個項目配合blog來講解的 這裡這就不細調了 CGAffineTransform transform = CGAffineTransformMakeScale(scaleTopView, scaleTopView ); CGFloat ty = (scaleTopView - 1) * ScrollHeadViewHeight; self.topView.transform = CGAffineTransformTranslate(transform, 0, -ty * 0.2); //記錄selectViewY軸的偏移量,這個是用來計算每次切換tableView,讓新出來的tableView總是在頭部用的, //現在腦子有點迷糊 算不出來了。。凌晨2.57分~ CGFloat selectViewOffsetY = self.selectView.frame.origin.y - ScrollHeadViewHeight; if (selectViewOffsetY != -ScrollHeadViewHeight && selectViewOffsetY = (0.5 + index)) { [self.selectView lineToIndex:index + 1]; } else if (seleOffsetX < 0 && offsetX / WNXAppWidth <= (0.5 + index)) { [self.selectView lineToIndex:index]; } } }
下面是一個scrollView上添加了3個tabelView,根據服務器返回的數據判斷顯示多少個,這裡就指顯示了倆個,並且第二個頁面還沒有來得及做
這些就是這個項目的大體思路,當然還有很多很多的細節都在代碼中,第一次嘗試將思路寫出來,感覺有很多不足,本應該每寫完一個功能就總結一下,而我是在發布的晚上回頭總結的,有很多當時的思路不是很清晰了...以後我會改善的,大家有什麼意見可以直接留言,我看到會一一回復的!
請直接打開WNXHuntForCity.xcworkspace
打開
而不要打開WNXHuntForCity.xcodeproj
附上代碼的下載地址
希望大家可以點一下右上角star,大家的支持與鼓勵是我繼續分享的動力^_^,歡迎大家多提意見和交流
我的微博鏈接