iOS高仿App源碼:10天時間純代碼打造高仿優質《內涵段子》
Github 地址 https://github.com/Charlesyaoxin/NeiHanDuanZI
介紹:
花了兩周閑余時間模仿了一下今日頭條旗下的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];
}</code></pre>
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;</code></pre>
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;</code></pre>
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;
}</code></pre>
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</code></pre>
主要實現的功能如下:
首頁 : 包括點贊、踩、分享、收藏,復制鏈接,視頻的播放,上拉下拉,評論列表,關注列表
首頁:
要點處理:將請求到的列表數據,轉化為模型數組,然后計算出模型所對應的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地址 布局,處理鍵盤彈出下落通知事件,當鍵盤申彈出和下落的時候更新約束,可以看下標哥的這篇軟文 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</code></pre>
首頁最復雜的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;</code></pre>
審核,利用貝塞爾完成一些展示上的效果
(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:@""];
}</code></pre>
首頁滑動穿透效果
// 滑動進度
(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);
}</code></pre>
附上自定義的一些類,項目中有自定義的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;</code></pre>
簡單易用的tableViewControllerGithub地址
分析和總結
-
這個項目做得時間比較倉促,前后用了不到兩周的時間。
-
不知道仔細看的朋友有沒有意識到,這是用純代碼寫的,并不是自己不習慣用nib或者sb,是因為一直以來想用純代碼寫一個項目。
-
所有的東西都是在公司的事情忙完的情況下編寫的,最近公司不是特別忙,所以有時間寫點自己的東西,當然下班回家晚上也花了不少時間用在了這個項目上面。
-
項目中有些類和文件是之前自己整理的直接拖進去用,一定的意義上來說節省了時間。
-
bug有很多,我也沒怎么測直接就提交Github了,以后肯定會再更新這個項目吧
-
下一階段的方向大概是swift項目了,現在在著手一個swift小項目,前段時間寫的,大概75%完成度了,也會在未來開源出來
來自:http://www.cocoachina.com/ios/20160914/17576.html