這是一款非常完整的一個(gè)ios項(xiàng)目,基本實(shí)現(xiàn)了我們常用的一些功能了,而且界面設(shè)計(jì)個(gè)人感覺還是挺不錯(cuò)的,是一個(gè)不錯(cuò)的學(xué)習(xí)ios項(xiàng)目,喜歡的朋友可以參考一下吧。
 項(xiàng)目展示,由于沒有數(shù)據(jù),所以所有的cell顯示的都是我自己寫的數(shù)據(jù)。 源碼下載:      
  抽屜   
  首頁部分效果   
   首頁效果  
   部分效果  
   發(fā)現(xiàn)  
   消息  
  搜索   
   設(shè)置  
   模糊效果  
   代碼注釋展示  
   代碼注釋展示 
 還有很多細(xì)節(jié)就不一一展示了,大家將代碼運(yùn)行下自己查看即可。由于內(nèi)容比較多,我就按功能模塊來介紹給大家了。 
 首先是左邊抽屜的效果以及點(diǎn)擊按鈕切換控制器 
 - 實(shí)際這里相當(dāng)于自己定義一個(gè)和系統(tǒng)UITabBarController差不多功能的控件,在最底層有一個(gè)控制器(后面稱之為主控制器),將左邊的按鈕view添加到主控制器的view上,創(chuàng)建好右邊有所的控制器(首頁,發(fā)現(xiàn),消息,設(shè)置...)并且將每個(gè)右邊控制器包裝一個(gè)導(dǎo)航控制器,將導(dǎo)航控制器按序添加給主控制器做子控制器,默認(rèn)情況下將首頁的導(dǎo)航控制器的view添加給主控制器的view子控件,根據(jù)左邊按鈕的點(diǎn)擊事件通過代理方法.移除舊控制器view從父視圖,將新的view添加到主視圖的view具體代碼如下,用一個(gè)臨時(shí)屬性之前選中的控制器
 
 //暫時(shí)先做沒有登陸的情況的點(diǎn)擊 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做形變動(dòng)畫完成的,由于每個(gè)導(dǎo)航控制器的功能一樣,這里抽取了共同的特點(diǎn)封裝了一個(gè)基類導(dǎo)航控制器,點(diǎn)擊左邊的按鈕完成抽屜效果
 - 拖動(dòng)手勢是給主控制器添加一個(gè)UipanGestureRecognizer手勢,根據(jù)拖動(dòng)的距離計(jì)算出該停留在哪里的位置,這里判斷很多,具體實(shí)現(xiàn)我在代碼中每一步都有注釋,參照代碼即可
 
 首頁 
 - 首頁就是一個(gè)tableView就可以搞定,tableView的headView顏色和數(shù)據(jù)服務(wù)器會(huì)給返回,給每個(gè)headView添加一個(gè)點(diǎn)擊手勢,點(diǎn)擊push到下一個(gè)控制器,導(dǎo)航條的顏色會(huì)和前一個(gè)headView的顏色一樣,,這里由于我之前設(shè)置了導(dǎo)航控制器的主題
 
 [UINavigationBar appearanceWhenContainedIn:self, nil] 
 - 以不可以直接設(shè)置導(dǎo)航條的顏色了 這里我嘗試了設(shè)置navigationBar的背景色,設(shè)置navigationBar的setTintColor:
 
 
 設(shè)置navigationBar.layer的背景色 以及根據(jù)顏色畫出navigationBar的背景圖片4種辦法都無法達(dá)到原生的效果 
 最后采用將navigationBar隱藏,自己放一個(gè)View了冒充導(dǎo)航條來解決這個(gè)問題 
 發(fā)現(xiàn) 
 - 這個(gè)頁面是一個(gè)UICollectionView,里面有兩組數(shù)據(jù),每一組都一個(gè)一個(gè)headView,需要注意的就是cell的點(diǎn)擊事件,這里注意了下官方的做法是不論點(diǎn)擊了cell的哪個(gè)位置,都會(huì)使cell內(nèi)部的button進(jìn)入高亮狀態(tài),這里需要用到事件的響應(yīng)鏈,在cell的內(nèi)部攔截整個(gè)cell的點(diǎn)擊事件都交給按鈕來做,具體代碼如下
 
 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { /* 攔截事件響應(yīng)者,不論觸發(fā)了cell中的哪個(gè)控件都交給iconButton來響應(yīng) */ // 1.判斷當(dāng)前控件能否接收事件 if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil; // 2. 判斷點(diǎn)在不在當(dāng)前控件 if ([self pointInside:point withEvent:event] == NO) return nil; return self.iconButton; } 
 但 這里需要注意這樣攔截cell的點(diǎn)擊事件,在collectionView的cell被點(diǎn)擊觸發(fā)didDeselectItemAtIndexPath: 就不會(huì)被觸發(fā)了,我的解決方法是在點(diǎn)擊button時(shí)通過代理方法傳給collectionView,外部在通過知道點(diǎn)擊了那個(gè)cell,push到下一 個(gè)控制器,并將cell的model賦值給下一個(gè)控制器 
 登陸(登陸只用了微信和新浪登陸,不涉及到注冊就非常簡單,這些只需去官網(wǎng)下來登陸和分享的sdk集成進(jìn)來即可,我一般使用友盟平臺(tái),包括崩潰統(tǒng)計(jì),三方登陸,分享,用戶分析等等) 
 消息 
 - 一樣這里也是tabelView,這里我個(gè)人的邏輯是將所有的消息歸檔到本地,每次點(diǎn)擊刪除一條,將本地的數(shù)據(jù)刪除一條,重新歸檔
 
 
 當(dāng)點(diǎn)擊刪除全部的時(shí)候,就清空本地的歸檔數(shù)據(jù),下次接受的服務(wù)器的數(shù)據(jù)在重新寫入 
 因?yàn)槭悄M的數(shù)據(jù),為了保障每次進(jìn)來都有數(shù)據(jù),就沒有實(shí)現(xiàn)歸檔解檔的操作,所以每次刪除后重新進(jìn)入會(huì)再次有數(shù)據(jù) 
 - 這里記錄編輯按鈕的狀態(tài),讀取本地是否有未讀消息數(shù)組的個(gè)數(shù),如果有就顯示編輯按鈕,記錄編輯按鈕的狀態(tài),如果是選中狀態(tài)就隱藏>圖片,顯示刪除按鈕,點(diǎn)擊刪除按鈕就將本地的數(shù)據(jù)數(shù)組刪除掉并且刷新tableView,這里用的是刪除動(dòng)畫,需要注意刪除的順序
 
 [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]; });  搜索
  
  搜索  
 - 這個(gè)也需要持久化存儲(chǔ)功能,每次頁面彈出后,先從本地讀取用戶歷史搜索的數(shù)據(jù),用戶每次刪除或者新輸入收縮內(nèi)容時(shí)也直接寫入到本地的caches文件中
 - 這里需要提一下關(guān)于熱門按鈕的布局,因?yàn)闊衢T的文字長度不一樣,但每次只有4個(gè)按鈕,在xib中先將按鈕的位置約束好,不過寬度的約束需要倆個(gè),一個(gè)是>= 和<= 然后根據(jù)服務(wù)器返回的實(shí)際長度在設(shè)置按鈕title時(shí),計(jì)算出每個(gè)按鈕的真實(shí)寬度,根據(jù)真實(shí)寬度算出間距是多少,重新布局一次按鈕的位置
 
 (void)setHotDatas:(NSMutableArray *)hotDatas { _hotDatas = hotDatas; //判斷是長度是否是4,開發(fā)中可以這樣寫 應(yīng)該服務(wù)器返回幾條數(shù)據(jù)就賦值多少,而不是固定的寫死數(shù)據(jù), //萬一服務(wù)器返回的數(shù)據(jù)有錯(cuò)誤,會(huì)造成用戶直接閃退的,有 //時(shí)在某些不是很重要的東西無法確定返回的是否正確,建議用 //@try @catch來處理, //即便返回的數(shù)據(jù)有誤,也可以讓用戶繼續(xù)別的操作, //而不會(huì)在無關(guān)緊要的小細(xì)節(jié)上造成閃退 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); }]; } 
 模糊效果  
   模糊效果 
 - 這里由于圖片的質(zhì)量被壓縮的太厲害,實(shí)際的效果還不錯(cuò),這個(gè)功能是iOS8新開放的接口,有倆個(gè)圖片特效可以使用,一個(gè)是模糊blur,一個(gè)是顏色疊加(類似于PS中圖片疊加的效果,不過就3種效果)
 
 詳情頁  
  詳情頁展示
   
 - 這個(gè)頁面挺坑的,需要注意的細(xì)節(jié)太多,也是我耗時(shí)最久的頁面,誠然目前bug依舊不少
 - 這個(gè)頁面的層級關(guān)系很重要,需要重點(diǎn)注意
 - 首先是導(dǎo)航條,這個(gè)咋一看好像是導(dǎo)航條有個(gè)漸隱漸現(xiàn)的動(dòng)畫,我的做法是在頂部放了一個(gè)高度為64的view,根據(jù)tableView的偏移量計(jì)算出view的透明度,但是透明度只是1或者0,頂部的scrollView里面裝的imageView,根據(jù)服務(wù)器返回的圖片地址個(gè)數(shù),設(shè)置他的展示內(nèi)容大小,并且在整一個(gè)scrollView最上面添加一個(gè)和導(dǎo)航條一樣顏色的view,用它來做出向上推出現(xiàn)綠色的效果,并且根據(jù)底部scrollview的偏移計(jì)算拉伸的大小,我這里拉伸的大小不是很準(zhǔn)確,感覺需要將錨點(diǎn)釘在最頂端。
 - 然后是中間切換tableView的view(后面就叫它選擇view),要實(shí)現(xiàn)能像headView一樣,卡在導(dǎo)航條下面的效果,這里因?yàn)闆]有導(dǎo)航條,并且在切換tableView時(shí)候不會(huì)帶走選擇view,所以只能將他放到和頂部的view在同一個(gè)層級中,同樣根據(jù)底部scrollView的contentOffset.y計(jì)算他的位置,當(dāng)偏移量超過頂部的64時(shí),就停留在那,不超過時(shí)就回到頂部view的下面,這里的計(jì)算我加了很多的注釋,怕計(jì)算的朋友也會(huì)看的懂的,大概是這樣
 - 下面是一個(gè)scrollView上添加了3個(gè)tabelView,根據(jù)服務(wù)器返回的數(shù)據(jù)判斷顯示多少個(gè),這里就指顯示了倆個(gè),并且第二個(gè)頁面還沒有來得及做
 
   -(void)scrollViewDidScroll:(UIScrollView *)scrollView { if (scrollView == self.rmdTableView || scrollView == self.infoTableView) {//說明是tableView在滾動(dòng) //記錄當(dāng)前展示的是那個(gè)tableView self.showingTableView = (UITableView *)scrollView; //記錄出上一次滑動(dòng)的距離,因?yàn)槭窃趖ableView的contentInset中偏移的ScrollHeadViewHeight,所以都得加回來 CGFloat offsetY = scrollView.contentOffset.y; CGFloat seleOffsetY = offsetY - self.scrollY; self.scrollY = offsetY; //修改頂部的scrollHeadView位置 并且通知scrollHeadView內(nèi)的控件也修改位置 CGRect headRect = self.topView.frame; headRect.origin.y -= seleOffsetY; self.topView.frame = headRect; //根據(jù)偏移量算出alpha的值,漸隱,當(dāng)偏移量大于-180開始計(jì)算消失的值 CGFloat startF = -180; //初始的偏移量Y值為 頂部倆個(gè)控件的高度 CGFloat initY = SelectViewHeight + ScrollHeadViewHeight; //缺少的那一段漸變Y值 CGFloat lackY = initY + startF; //自定義導(dǎo)航條高度 CGFloat naviH = 64; //漸隱alpha值 CGFloat alphaScaleHide = 1 - (offsetY + initY- lackY) / (initY- naviH - SelectViewHeight - lackY); //漸現(xiàn)alph值 CGFloat alphaScaleShow = (offsetY + initY - lackY) / (initY - naviH - SelectViewHeight - lackY) ; if (alphaScaleShow >= 0.98) { //顯示導(dǎo)航條 [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; /* 這段代碼很有深意啊。。最開始是直接用偏移量算的,但是回來的時(shí)候速度比較快時(shí)偏移量會(huì)偏度很大 然后就悲劇了。換了好多方法。。最后才開竅T——T,這一段我會(huì)在blog里面詳細(xì)描述我用的各種錯(cuò)誤的方法 用了KVO監(jiān)聽偏移量的值,切換了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; //算出頭部的變形 這里的動(dòng)畫不是很準(zhǔn)確,好的動(dòng)畫是一點(diǎn)一點(diǎn)試出來了 這里可能還需要配合錨點(diǎn)來進(jìn)行動(dòng)畫,關(guān)于這種動(dòng)畫我會(huì)在以后單開一個(gè)項(xiàng)目配合blog來講解的 這里這就不細(xì)調(diào)了 CGAffineTransform transform = CGAffineTransformMakeScale(scaleTopView, scaleTopView ); CGFloat ty = (scaleTopView - 1) * ScrollHeadViewHeight; self.topView.transform = CGAffineTransformTranslate(transform, 0, -ty * 0.2); //記錄selectViewY軸的偏移量,這個(gè)是用來計(jì)算每次切換tableView,讓新出來的tableView總是在頭部用的, //現(xiàn)在腦子有點(diǎn)迷糊 算不出來了。。凌晨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]; } } } 
 這些就是這個(gè)項(xiàng)目的大體思路,當(dāng)然還有很多很多的細(xì)節(jié)都在代碼中,第一次嘗試將思路寫出來,感覺有很多不足,本應(yīng)該每寫完一個(gè)功能就總結(jié)一下,而我是在發(fā)布的晚上回頭總結(jié)的,有很多當(dāng)時(shí)的思路不是很清晰了...
  
 請直接打開WNXHuntForCity.xcworkspace  
   打開 
 而不要打開WNXHuntForCity.xcodePRoj 
 
 
  |