说明
首先声明,今日头条是我经常用的 app 之一,模仿今日头条也是因为感兴趣,代码仅用于学习交流。对于项目中的数据接口都是通过 Charles 抓包获得,基本每个界面都是有数据请求,不会抓包的朋友可以看我 这一篇文章。
项目中有的地方代码写的不是很简洁,毕竟自己能力有限,对 Swift 使用不是很熟练,还请各位朋友不喜勿喷。下面有项目的完整源码,喜欢的朋友可以下载下来,如果您感觉我写的代码对您有所帮助,还请在 github 给个 star,非常感谢您的支持!~
对于代码中出现的问题,可以及时联系我,我会继续修改。
github 地址
CodeData 地址
环境设置
项目环境
- Xcode 7.3.1(低于这个版本会报错)。
- Swift 2.2
- iOS 8.0 +
使用 cocoaPods 管理第三方库, 如果电脑没有安装 cocoapods,请先安装 cocoapods。安装方式可参考:最新版 CocoaPods 的安装流程
项目中使用到的第三方库
实现的功能
- 获取今日头条的接口
- 完成首页的布局和数据的显示
- 实现首页顶部导航栏滚动
- 新闻详情界面简单实现
- 点击屏蔽按钮,弹出屏蔽视图(坐标有一些问题)
- 完成视频界面顶部导航栏滚动
- 完成视频界面布局和数据获取
- 用户界面简单实现
- 完成关注界面布局和数据的获取
- 完成关注界面,添加关注功能
- 完成搜索功能
- 完成个人界面的布局
- 完成设置界面的布局
- 完成离线下载界面布局
- 活动界面简单实现
- 登录界面的简单实现
- 启动界面的简单实现
数据请求
今日头条的接口文件请看: news.json,需要提前安装 postman,然后把该文件导入到 postman 进行查看,可以打开谷歌浏览器,找到扩展程序,添加新的扩展,搜索 postman。
下载地址请看 postman,下载完成后,直接拖入到谷歌浏览器的扩展程序界面即可。
数据请求的具体方式,请看 YMNetworkTool.swift。
首页
YMHomeViewController.md
1.首先,首页的状态栏的颜色是白色,所以调用了下面的方法:
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .LightContent
}
但是,经过测试,上面的代码不起作用,对于 YMMineViewController.swift
上面的代码是起作用的。
唯一的区别是就是在 YMMineViewController.swift
中隐藏了导航条。所以经过查阅资料,得到下面的结论:
1.不管是调用了系统的
UINavigationController
还是使用自己继承自UINavigationController
,如果navigationBar
没有被隐藏的话,那么导航控制器的rootController
以及它push
的控制器的preferredStatusBarStyle()
方法都不会被调用。
2.如果在当前控制器手动设置了navagationBar
的barStyle
为.Black
或者.Default
或者使用下面的代码手动设置:
// 方式1
navigationController?.setNavigationBarHidden(true,animated: false)
// 方式2
navigationController?.navigationBarHidden = true
那么 preferredStatusBarStyle()
就会被正常调用了。
还有一点关于隐藏导航栏的注意点请看 YMMineViewController.swift。
2.关于导航栏的 titleView
在首页首页顶部标题的时候,直接设置 titleView 的宽度为屏幕的宽,但是两边总是会留出 10 的间距,这个时候需要重写父类的 setFrame 方法,在 OC 里面可以使用下面的方法:
- (void)setFrame:(CGRect)frame
{
CGRect newFrame = CGRectMake(0,0,SCREENW,44);
[super setFrame:frame];
}
但是在 swift 中不能这样写,要使用下面的方式:
/// 重写 frame
override var frame: CGRect {
didSet {
let newFrame = CGRectMake(0,44)
super.frame = newFrame
}
}
这样设置,运行程序,发现 titleView 在屏幕两边不在留有间距。
3.子控制器
YMHomeTopicController.swift
作为 YMHomeViewController.swift
的子控制器,显示新闻数据。
YMHomeTopicController.md
该类注册了四种 cell,分别表示中间三张图片,右边一张图片,中间一张大图,中间一张视频大图,没有图片的情况。
以下是四种情况:
在 tableView(tableView: UITableView,cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
中,根据不同情况对要显示的 cell 进行判断,显示对应的 cell。
具体判断情况请看 Model
里的 YMNewsTopic.swift
类。
详情有下面几种方式:
为了实现简单,这里使用 webView
来实现
YMPopPresentationController.md
iOS 8 以后推出的专门负责转场动画的控制器。
在 Xcode 7 以上的版本中,UIPresentationController
有一个 bug,见下图:
presentingViewController
会报一个野指针的错误,这是 Xcode 的 bug。
UIPresentationController
中有两个方法可以布局子视图,分别是:
// 即将布局转场子视图时调用
public func containerViewWillLayoutSubviews()
// 布局完成转场子视图时调用
public func containerViewDidLayoutSubviews()
可以在两个方法里设置 UIPresentationController
的容器视图 containerView
和 被展现的视图 presentedView()
。
YMHomeTopicCell.md
这个类主要作为四种类型 cell 的父类,主要定义了 标题、头像、昵称、评论、关闭按钮。
当时考虑过使用一个类来实现四种类型的 cell,但是经过测试,由于 cell 的重用机制,始终不能达到想要的结果,所以才分别创建了四种不同的 cell,使用一个类的方式是 YMTopicTableViewCell.swift
这个类,大家可以参考一下。
由于首页的情况比较多,cell 的显示比较复杂,而且今日头条的接口也不是很规范,所以这几个类实现起来比较麻烦,而且代码写的不是很简洁,用了很多 if
判断,可能看起来不是很美观。
判断的情况与 YMNewsTopic.swift
类相同,具体请看 YMNewsTopic.swift
。
我觉得使用不同 cell 的情况还比较简单理解。
如果各位朋友有什么更好的实现方法,欢迎给我留言或『Pull Request』,非常感谢您的留言和建议。
YMScrollTitleView.md
这个类和和视频顶部标题的类有些类似,对于数据和按钮点击的回调使用闭包的方式。而在视频的标题 YMVideoTitleView.swift
里使用代理来代替闭包,实现的功能是相同的,但是实现的方式不同,可以对比看一下。
对控件的布局方式还是使用的 SnapKit
来进行布局。
这个类里需要首先从服务器获取标题数据,服务器返回一个数组,根据这个数组循环创建标题的 label
,然后设置好 label
的位置以及 scrollView
的 contentSize
。
标题 label 的点击通过添加手势来实现监听点击操作,titleLabelOnClick
为标题点击的方法,当点击的时候,根据索引进行相应的偏移,调用 adjustTitleOffSetToCurrentIndex
来改变 label 的位置。
在 adjustTitleOffSetToCurrentIndex(currentIndex: Int,oldIndex: Int)
方法里,需要获取之前点击 label 的索引以及刚刚点击 label 的索引,改变形变,计算当前的偏移量。
重写 frame,来设置导航栏不再有两边的间距。请看具体代码 206 行。
YMNewsTopic.md
这个类是我觉得最麻烦的一个类了,有很多种情况,所以判断也比较多。
今日头条返回的数据中,有这四个字段,image_list
,middle_image
,large_image_list
,video_detail_info
,在 cell 里面分别对应 YMHomeSmallCell.swift
,YMHomeMiddleCell.swift
,YMHomeLargeCell.swift
。
image_list
这是一个数组,表示中间有三种图的情况;
middle_image
这是一个字典,表示图片在右侧的情况;
large_image_list
这是一个数组,表示中间是一张大图;
video_detail_info
这是一个字典,表示是视频,中间也用一张大图表示,这种情况和大图的情况基本相同,但是视频中间多了播放按钮。
还有最后一种情况就是没有图片的情况,比如置顶的专题,但是置顶的专题和上面在举报按钮的地方也有所区别,置顶的新闻没有举报按钮,其他情况有举报按钮,需要根据 一个字段 label
来进行判断。
上面五种情况出现的依赖关系,也需要进行判断,
下面说一下,具体的判断过程:
image_list | middle_image | large_image_list | video_detail_info | @H_62_404@
---|---|---|---|
nil | nil | nil | nil | @H_62_404@
nil | 不为 nil | 不为 nil | nil | @H_62_404@
nil | 不为 nil | nil | nil | @H_62_404@
不为 nil | 不为 nil | 不为 nil | 不为 nil | @H_62_404@
不为 nil | 不为 nil | 不为 nil | nil | @H_62_404@
不为 nil | 不为 nil | nil | 不为 nil | @H_62_404@
不为 nil | 不为 nil | nil | nil | @H_62_404@