簡書地址:簡書入口
9.16日提示 修復了一下首頁因為數據原因引起的崩潰問題。內涵段子服務數據結構復雜,有些判斷邏輯可能不是很嚴謹,造成了崩潰,大家可以下載最新版本的
介紹:
花了兩周的閒余時間模仿了一下今日頭條旗下的iOS端app內涵段子,如果喜歡的話請給個star。(8.30-9.11)
這個項目是用OC編寫,如果有的朋友已經下載下來看了這個項目, 就會意識到這個項目沒有一個storyboard或者是nib,不是因為不喜歡用storyboard或者nib,而是因為一直以來就想用純代碼寫個項目,(好遠大的夢想。。開玩笑的。。),但是項目是寫出來的,光想不做不寫是不行的,所以我就開始我的”內涵之旅“了。
日志:
8.30號:沒怎麼做東西,就是搭建了項目的架構,拉入了之前經常用的一些工具類,宏定義等等。 8.30主要事項:UITabbarController+UINavigationController項目架構組建。 部分代碼
// 添加子控制器 - (void)addChildViewControllerWithClassname:(NSString *)classname imagename:(NSString *)imagename title:(NSString *)title { UIViewController *vc = [[NSClassFromString(classname) alloc] init]; NHBaseNavigationViewController *nav = [[NHBaseNavigationViewController alloc] initWithRootViewController:vc]; nav.tabBarItem.title = title; nav.tabBarItem.image = [UIImage imageNamed:imagename]; nav.tabBarItem.selectedImage = [[UIImage imageNamed:[imagename stringByAppendingString:@"_press"]] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; [self addChildViewController:nav]; }
8.31號:開始在8.30建的類上面填充內容,首頁,這個最復雜的界面。 搭建類似於今日頭條首頁的架構。開始抓接口,添加接口的公共參數,完善請求基類。還有幾個展示的列表頁的編寫,由簡單入難,有助於在開發中培養自信心。
/** 鏈接*/ @property (nonatomic, copy) NSString *nh_url; /** 默認GET*/ @property (nonatomic, assign) BOOL nh_isPost; /** 圖片數組*/ @property (nonatomic, strong) NSArray *nh_imageArray; /** 構造方法*/ + (instancetype)nh_request; + (instancetype)nh_requestWithUrl:(NSString *)nh_url; + (instancetype)nh_requestWithUrl:(NSString *)nh_url isPost:(BOOL)nh_isPost; + (instancetype)nh_requestWithUrl:(NSString *)nh_url isPost:(BOOL)nh_isPost delegate:(id )nh_delegate; /** 開始請求,如果設置了代理,不需要block回調*/ - (void)nh_sendRequest; /** 開始請求,沒有設置代理,或者設置了代理,需要block回調,block回調優先級高於代理*/ - (void)nh_sendRequestWithCompletion:(NHAPIDicCompletion)completion;
9.1號, 控制器和cell以及普通文本圖片數據的展示,以及發布界面的視圖封裝。 9.2 - 9.4 首頁的回調處理以及發現界面
typedef NS_ENUM(NSUInteger, NHHomeTableViewCellItemType) { /** 點贊*/ NHHomeTableViewCellItemTypeLike = 1, /** 踩*/ NHHomeTableViewCellItemTypeDontLike, /** 評論*/ NHHomeTableViewCellItemTypeComment, /** 分享*/ NHHomeTableViewCellItemTypeShare }; @class NHHomeTableViewCellFrame , NHHomeTableViewCell, NHDiscoverSearchCommonCellFrame, NHNeiHanUserInfoModel; @protocol NHHomeTableViewCellDelegate /** 點擊浏覽大圖*/ - (void)homeTableViewCell:(NHHomeTableViewCell *)cell didClickImageView:(UIImageView *)imageView currentIndex:(NSInteger)currentIndex urls:(NSArray *)urls; /** 播放視頻*/ - (void)homeTableViewCell:(NHHomeTableViewCell *)cell didClickVideoWithVideoUrl:(NSString *)videoUrl videoCover:(NHBaseImageView *)baseImageView; /** 分類*/ - (void)homeTableViewCellDidClickCategory:(NHHomeTableViewCell *)cell; /** 個人中心*/ - (void)homeTableViewCell:(NHHomeTableViewCell *)cell gotoPersonalCenterWithUserInfo:(NHNeiHanUserInfoModel *)userInfoModel; /** 點擊底部item*/ - (void)homeTableViewCell:(NHHomeTableViewCell *)cell didClickItemWithType:(NHHomeTableViewCellItemType)itemType; @optional /** 點擊關注*/ - (void)homeTableViewCellDidClickAttention:(NHHomeTableViewCell *)cell; /** 刪除*/ - (void)homeTableViewCellDidClickClose:(NHHomeTableViewCell *)cell; @end @interface NHHomeTableViewCell : NHBaseTableViewCell /** 代理*/ @property (nonatomic, weak) id delegate; /** 首頁cellFrame模型*/ @property (nonatomic, strong) NHHomeTableViewCellFrame *cellFrame; /** 搜索cellFrame模型*/ @property (nonatomic, strong) NHDiscoverSearchCommonCellFrame *searchCellFrame; /** 用來判斷是否有刪除按鈕*/ @property (nonatomic, assign) BOOL isFromHomeController; /** 判斷是否在詳情頁*/ - (void)setCellFrame:(NHHomeTableViewCellFrame *)cellFrame isDetail:(BOOL)isDetail; /** 設置關鍵字*/ - (void)setSearchCellFrame:(NHDiscoverSearchCommonCellFrame *)searchCellFrame keyWord:(NSString *)keyWord; /** 點贊*/ - (void)didDigg; /** 踩*/ - (void)didBury;
9.5 - 9.7審核界面的邏輯處理和動畫處理,以及發現界面的輪播圖和自定義pageControl
- (void)setCurrentIndex:(NSInteger)currentIndex { _currentIndex = currentIndex; UIBezierPath *path = [UIBezierPath bezierPath]; // 設置選中layer的動畫 CGFloat delta = self.width - self.numberOfItems * self.pageWidth + (self.numberOfItems - 1) * self.pageSpace - 15; [path moveToPoint:CGPointMake(currentIndex * self.pageWidth + currentIndex * self.pageSpace + delta, 5)]; [path addLineToPoint:CGPointMake((currentIndex + 1) * self.pageWidth + currentIndex * self.pageSpace + delta , 5)]; // path(平移動畫) CGFloat duration = 1.0; CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"]; animation.removedOnCompletion = NO; animation.fillMode = kCAFillModeForwards; animation.duration = duration; animation.fromValue = (__bridge id _Nullable)(self.prePath.CGPath); animation.toValue = (__bridge id _Nullable)(path.CGPath); [self.selectedLayer addAnimation:animation forKey:@""]; self.prePath = path; } - (void)setNumberOfItems:(NSInteger)numberOfItems { _numberOfItems = numberOfItems; if (self.pageWidth * numberOfItems + self.pageSpace * (numberOfItems - 1) > self.frame.size.width) { self.pageWidth = (self.frame.size.width - self.pageSpace * (numberOfItems - 1)) / numberOfItems; } CGFloat originX = 0; UIBezierPath *path = [UIBezierPath bezierPath]; // 內容充不滿,需要靠右邊對齊 CGFloat delta = self.width - numberOfItems * self.pageWidth + (numberOfItems - 1) * self.pageSpace - 15; for (int i = 0; i < numberOfItems; i++) { originX = i * self.pageSpace + self.pageWidth * i + delta; [path moveToPoint:CGPointMake(originX, 5)]; [path addLineToPoint:CGPointMake(originX + self.pageWidth, 5)]; path.lineWidth = 5; if (i == 0) { self.prePath = path; self.selectedLayer.path = self.prePath.CGPath; } } self.showPageLayer.path = path.CGPath; }
9.8 - 9.9,搜索界面的邏輯處理 個人中心內容的填充,部分公共空數據界面視圖的處理 9.10 視頻的播放和一些地方的修修補補 9.11部分動畫效果的完善,例如點贊和踩,關注等。。以及簡單的測試。9.11晚上編寫博文上傳Github。
@interface NHCustomCommonEmptyView : UIView @property (nonatomic, weak) UIImageView *topTipImageView; @property (nonatomic, weak) UILabel *firstL; @property (nonatomic, weak) UILabel *secondL; - (instancetype)initWithTitle:(NSString *)title secondTitle:(NSString *)secondTitle iconname:(NSString *)iconname; - (instancetype)initWithAttributedTitle:(NSMutableAttributedString *)attributedTitle secondAttributedTitle:(NSMutableAttributedString *)secondAttributedTitle iconname:(NSString *)iconname; - (void)showInView:(UIView *)view; @end
主要實現的功能如下:
首頁 : 包括點贊、踩、分享、收藏,復制鏈接,視頻的播放,上拉下拉,評論列表,關注列表
首頁:
要點處理:將請求到的列表數據,轉化為模型數組,然後計算出模型所對應的frame數組,這樣做的好處是防止CellForHeight會計算多次,缺點是計算量大, 需要耐心。
利用視圖的drawRect方法來達到滾動條滑動的時候的穿透效果,封裝分享視圖,見NHHomeShareView ,封裝帶有高斯模糊效果的自定義彈窗,與系統的UIAlertView相差無幾,效果更佳。
評論列表:利於YYLabel和NSAttributeString,將@的用戶的名字高亮,加以點擊事件。
分享: 封裝分享管理類,配置友盟的appKey和UrlScheme等一系列必要操作。
圖片浏覽器,根據數據展示布局九宮格視圖,然後利於自定義的NHBaseImgeView,將網絡圖片的請求處理邏輯全部放到該類中,還記得當SDWebimage的方法加上sd_開頭的時候,我們吃過的虧麼?
Gif圖的處理,封裝一個Gif視圖,繼承自UIImageView,然後頂部加載loading。
發現:輪播,熱吧列表,推薦的關注用戶列表,訂閱列表,搜索,附近的人,附近的人的篩選。
發現:
要點處理:利用UICollectionview實現無限滾動輪播視圖,利於貝塞爾曲線自定義pageControl,類似於系統的UIPageControl,當改變當前索引的時候,曲線改變,設置layer的動畫。
附近的人:思路:當app啟動的時候先請求一次定位信息,如果請求到了將經緯度保存,然後如果進入附近的人重新定位,獲取最新的經緯度,獲取附近的人列表,封裝篩選視圖,根據性別篩選附近的人。
搜索:自定義搜索框,如果業務邏輯比較深的話,用系統的UISearchBar就不太現實了,需要讓搜索框變得變得高度可定制化。搜索關鍵字,將搜索結果的文本轉化為富文本,自定義多種不同類型的cell,然後顯示數據,處理業務邏輯。要點在於,搜索的時候需要同時並發調用三個接口,搜索用戶、動態還有熱吧.
這時候處理單個界面的多個並發網絡請求用到了dispatch_group想了解GCD可點擊此鏈接, 當然,如果你的項目使用的RAC,那麼這個dispatch_group,就可以摒棄了。
審核:舉報,喜歡和不喜歡,手動左滑刷新,利用貝塞爾曲線和CAShaperLayer加載視圖動畫。
審核
處理:可以右滑來查看新的內涵段子動畫,詳情見下面Gif圖。利於UICollectionview進行頁面展示,自定義UICollectionviewFlowLayout布局。
封裝舉報底部視圖
利於UIWebView加載Gif圖,這裡的處理不是很好。
封裝一個帶有loading進度條的時候,loading進度條的實現使用了CAShapeLayer和白塞爾曲線以及基本動畫,詳情可以去項目中的NHCheckTableViewProgressBar這個類。
發布:選擇熱吧,發布圖片文字。
發布
發布界面相對簡單,利用masonry地址布局,處理鍵盤彈出下落通知事件,當鍵盤申彈出和下落的時候更新約束,可以看下標哥的這篇軟文masonry約束動畫。
利於UICollectionview布局圖片選擇完成後的界面,添加帶有占位文字的高度可定制化的textView。
用戶:用戶信息寫死在本地,模仿登錄邏輯。
用戶
將用戶信息利用歸檔存儲在本地,用NSUserdefault記錄用戶是否在登陸狀態。
修改頭像,利用彈出的自定義的ActionSheet,詳情可見NHCustomActionSheet類。
項目中工具類眾多,管理類也眾多,如果您有需要或者是想了解的話可以去Github查看我的項目源碼,還有幾個比較好用的Demo也開源了。
代碼展示
@protocol NHBaseRequestReponseDelegate @required /** 如果不用block返回數據的話,這個方法必須實現*/ - (void)requestSuccessReponse:(BOOL)success response:(id)response message:(NSString *)message; @end typedef void(^NHAPIDicCompletion)(id response, BOOL success, NSString *message); @interface NHBaseRequest : NSObject @property (nonatomic, weak) id nh_delegate; /** 鏈接*/ @property (nonatomic, copy) NSString *nh_url; /** 默認GET*/ @property (nonatomic, assign) BOOL nh_isPost; /** 圖片數組*/ @property (nonatomic, strong) NSArray *nh_imageArray; /** 構造方法*/ + (instancetype)nh_request; + (instancetype)nh_requestWithUrl:(NSString *)nh_url; + (instancetype)nh_requestWithUrl:(NSString *)nh_url isPost:(BOOL)nh_isPost; + (instancetype)nh_requestWithUrl:(NSString *)nh_url isPost:(BOOL)nh_isPost delegate:(id )nh_delegate; /** 開始請求,如果設置了代理,不需要block回調*/ - (void)nh_sendRequest; /** 開始請求,沒有設置代理,或者設置了代理,需要block回調,block回調優先級高於代理*/ - (void)nh_sendRequestWithCompletion:(NHAPIDicCompletion)completion; @end
首頁最復雜的cell @class NHBaseImageView; typedef NS_ENUM(NSUInteger, NHHomeTableViewCellItemType) { /** 點贊*/ NHHomeTableViewCellItemTypeLike = 1, /** 踩*/ NHHomeTableViewCellItemTypeDontLike, /** 評論*/ NHHomeTableViewCellItemTypeComment, /** 分享*/ NHHomeTableViewCellItemTypeShare }; @class NHHomeTableViewCellFrame , NHHomeTableViewCell, NHDiscoverSearchCommonCellFrame, NHNeiHanUserInfoModel; @protocol NHHomeTableViewCellDelegate /** 分類*/ - (void)homeTableViewCellDidClickCategory:(NHHomeTableViewCell *)cell; /** 個人中心*/ - (void)homeTableViewCell:(NHHomeTableViewCell *)cell gotoPersonalCenterWithUserInfo:(NHNeiHanUserInfoModel *)userInfoModel; /** 點擊底部item*/ - (void)homeTableViewCell:(NHHomeTableViewCell *)cell didClickItemWithType:(NHHomeTableViewCellItemType)itemType; /** 點擊浏覽大圖*/ - (void)homeTableViewCell:(NHHomeTableViewCell *)cell didClickImageView:(UIImageView *)imageView currentIndex:(NSInteger)currentIndex urls:(NSArray *)urls; /** 播放視頻*/ - (void)homeTableViewCell:(NHHomeTableViewCell *)cell didClickVideoWithVideoUrl:(NSString *)videoUrl videoCover:(NHBaseImageView *)baseImageView; @optional /** 點擊關注*/ - (void)homeTableViewCellDidClickAttention:(NHHomeTableViewCell *)cell; /** 刪除*/ - (void)homeTableViewCellDidClickClose:(NHHomeTableViewCell *)cell; @end @interface NHHomeTableViewCell : NHBaseTableViewCell /** 代理*/ @property (nonatomic, weak) id delegate; /** 首頁cellFrame模型*/ @property (nonatomic, strong) NHHomeTableViewCellFrame *cellFrame; /** 搜索cellFrame模型*/ @property (nonatomic, strong) NHDiscoverSearchCommonCellFrame *searchCellFrame; /** 用來判斷是否有刪除按鈕*/ @property (nonatomic, assign) BOOL isFromHomeController;
審核,利用貝塞爾完成一些展示上的效果 - (void)setLeftScale:(CGFloat)leftScale { _leftScale = leftScale; NSInteger leftDelta = leftScale * 100; self.leftL.text = [NSString stringWithFormat:@"%ld%%", leftDelta]; CGFloat height = 10; UIRectCorner corner = UIRectCornerAllCorners; if (leftScale == 1.0) { corner = UIRectCornerAllCorners; } else { corner = UIRectCornerTopLeft | UIRectCornerBottomLeft; } UIBezierPath *bezierPath0 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, self.height / 2.0 - height / 2.0, 0, height) byRoundingCorners:corner cornerRadii:CGSizeMake(5.f, 5.f)]; UIBezierPath *bezierPath1 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, self.height / 2.0 - height / 2.0, self.width * self.leftScale, height) byRoundingCorners:corner cornerRadii:CGSizeMake(5.f, 5.f)]; CGFloat duration = 0.8; [self performSelector:@selector(showLeftAndRightLabel) withObject:nil afterDelay:duration]; CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"]; animation.removedOnCompletion = NO; animation.fillMode = kCAFillModeForwards; animation.duration = duration; animation.fromValue = (__bridge id _Nullable)(bezierPath0.CGPath); animation.toValue = (__bridge id _Nullable)(bezierPath1.CGPath); [self.leftLayer addAnimation:animation forKey:@""];
首頁滑動穿透效果// 滑動進度- (void)setProgress:(CGFloat)progress { _progress = progress; [self setNeedsDisplay]; } - (void)drawRect:(CGRect)rect { [super drawRect:rect]; [_fillColor set]; CGRect newRect = rect; newRect.size.width = rect.size.width * self.progress; UIRectFillUsingBlendMode(newRect, kCGBlendModeSourceIn); }
附上自定義的一些類,項目中有自定義的ActionSheet,AlertView,SegmentControl,pageControl等。
貼上幾段封裝的關於tableView的一些代碼 typedef NS_ENUM(NSInteger, NHBaseTableViewRowAnimation) { Fade = UITableViewRowAnimationFade, Right = UITableViewRowAnimationRight, // slide in from right (or out to right) Left = UITableViewRowAnimationLeft, Top = UITableViewRowAnimationTop, Bottom = UITableViewRowAnimationBottom, None = UITableViewRowAnimationNone, // available in iOS 3.0 Middle = UITableViewRowAnimationMiddle, // available in iOS 3.2. attempts to keep cell centered in the space it will/did occupy Automatic = 100 // available in iOS 5.0. chooses an appropriate animation style for you }; @class NHBaseTableViewCell; @interface NHBaseTableView : UITableView - (void)nh_updateWithUpdateBlock:(void(^)(NHBaseTableView *tableView ))updateBlock; - (UITableViewCell *)nh_cellAtIndexPath:(NSIndexPath *)indexPath; /** 注冊普通的UITableViewCell*/ - (void)nh_registerCellClass:(Class)cellClass identifier:(NSString *)identifier; /** 注冊一個從xib中加載的UITableViewCell*/ - (void)nh_registerCellNib:(Class)cellNib nibIdentifier:(NSString *)nibIdentifier; /** 注冊一個普通的UITableViewHeaderFooterView*/ - (void)nh_registerHeaderFooterClass:(Class)headerFooterClass identifier:(NSString *)identifier; /** 注冊一個從xib中加載的UITableViewHeaderFooterView*/ - (void)nh_registerHeaderFooterNib:(Class)headerFooterNib nibIdentifier:(NSString *)nibIdentifier; #pragma mark - 只對已經存在的cell進行刷新,沒有類似於系統的 如果行不存在,默認insert操作 /** 刷新單行、動畫默認*/ - (void)nh_reloadSingleRowAtIndexPath:(NSIndexPath *)indexPath; /** 刷新單行、動畫默認*/ - (void)nh_reloadSingleRowAtIndexPath:(NSIndexPath *)indexPath animation:(NHBaseTableViewRowAnimation)animation; /** 刷新多行、動畫默認*/ - (void)nh_reloadRowsAtIndexPaths:(NSArray *)indexPaths; /** 刷新多行、動畫默認*/ - (void)nh_reloadRowsAtIndexPaths:(NSArray *)indexPaths animation:(NHBaseTableViewRowAnimation)animation; /** 刷新某個section、動畫默認*/ - (void)nh_reloadSingleSection:(NSInteger)section; /** 刷新某個section、動畫自定義*/ - (void)nh_reloadSingleSection:(NSInteger)section animation:(NHBaseTableViewRowAnimation)animation; /** 刷新多個section、動畫默認*/ - (void)nh_reloadSections:(NSArray *)sections; /** 刷新多個section、動畫自定義*/ - (void)nh_reloadSections:(NSArray *)sections animation:(NHBaseTableViewRowAnimation)animation; #pragma mark - 對cell進行刪除操作 /** 刪除單行、動畫默認*/ - (void)nh_deleteSingleRowAtIndexPath:(NSIndexPath *)indexPath; /** 刪除單行、動畫自定義*/ - (void)nh_deleteSingleRowAtIndexPath:(NSIndexPath *)indexPath animation:(NHBaseTableViewRowAnimation)animation; /** 刪除多行、動畫默認*/ - (void)nh_deleteRowsAtIndexPaths:(NSArray *)indexPaths; /** 刪除多行、動畫自定義*/ - (void)nh_deleteRowsAtIndexPaths:(NSArray *)indexPaths animation:(NHBaseTableViewRowAnimation)animation; /** 刪除某個section、動畫默認*/ - (void)nh_deleteSingleSection:(NSInteger)section;
簡單易用的tableViewControllerGithub地址
分析和總結
這個項目做得時間比較倉促,前後用了不到兩周的時間。
不知道仔細看的朋友有沒有意識到,這是用純代碼寫的,並不是自己不習慣用nib或者sb,是因為一直以來想用純代碼寫一個項目。
所有的東西都是在公司的事情忙完的情況下編寫的,最近公司不是特別忙,所以有時間寫點自己的東西,當然下班回家晚上也花了不少時間用在了這個項目上面。
項目中有些類和文件是之前自己整理的直接拖進去用,一定的意義上來說節省了時間。
bug有很多,我也沒怎麼測直接就提交Github了,以後可能會再更新這個項目吧。
下一階段的方向大概是swift項目了,現在在著手一個swift小項目,前段時間寫的,大概75%完成度了,也會在未來開源出來,這個項目的話短期內不會再更新了。
如果想交朋友的可以加我qq3297391688,共同進步,成為一名真正的‘老司機’。
如果您喜歡這個項目,或者這個項目幫助到了您,請聯系我,或者給我點贊和評論吧。 代碼先不更新了,針對出現的問題,如果首頁出現崩潰現象,朋友們請重新運行一下。 想了解更多請移步至簡書地址。