田腾飞的博客

【iOS开发】怎么写出一个可复用的类网易新闻首页

这一篇继续写一篇UI层的东西吧,一直感觉iOS中网易新闻或者今日头条等新闻类的APP的UI比较特别,这篇文章就来讲解一下怎么写出一个可复用可扩展的类网易新闻首页框架。先看一下效果图:

Demo地址猛戳我 使用的是Swift 3编写。

好了还是那句话,其实实现原理看代码就行了,不需要再往下写了。但是像俺这种追求完美的男子,不允许文章就这么短,俺接着往下写,小伙伴们可以直接去看代码了,不要打扰俺装逼!🤒

首先上面那个分类的title肯定是一个UIScrollView或者是UICollectionView,这边我们使用的是UIScrollView和UIButton,当然你也可以使用UICollectionView去实现,下面这个是一个UICollectionView,每一个页面是一个cell,这样相同的页面可以得到复用,节省内存空间。

首先我们要去想想怎么去设计这个结构,上面的header(上面的分类控件)和下面的contentView(内容页面)会不会是一个整体?我们假设把上面的header控件和下面的内容控件绑定到一起,也就是放入一个View中,view暴露方法供外面调用,会有什么问题?

  • 假如我APP中还有一个这样的页面,但是上面的分类header使用的是segmentedControll,难道我还要去再写一个contentView去与他相配?
  • 假如我APP有一个地方只使用了上面的分类header呢?难道我要重写一个header?

可能理由你没有看懂?不过不要在意这些细节,在我的设计中就是多用组合,不要把上面的header控件和下面的content控件绑死了

所以我建了两个文件,一个headerView,一个ContentCollectionView。其中headerView就是上面的带有分类按钮的整体,ContentCollectionView就是下面有内容的整体。他们两个之间有交互,我们可以使用代理模式,每一个View暴露出代理方法供别的控件调用。

1
2
3
4
5
// headerView
protocol HeaderViewDelegate: NSObjectProtocol {
// 选中了某个按钮
func categoryHeaderView(headerView: HeaderView, selectedIndex: Int)
}
1
2
3
4
5
6
7
// ContentCollectionView
protocol ContentCollectionViewDelegate: NSObjectProtocol {
// 从某个页面滑动到某个页面
func contentCollectionView(_ contentView: ContentCollectionView, didScrollFrom fromIndex: Int, to toIndex: Int, scale: Float)
// 显示了某个页面
func contentCollectionView(_ contentView: ContentCollectionView, didShowViewWith index: Int)
}

然而这个问题解决了还有一个问题待解决。我的内容控件使用的是一个View,里面包装一个UICollectionView,但是如果我有些cell里面显示的tableView,然而有些cell里面要显示的ScrollView,有些cell里面要显示的是一个普通的View,我怎么知道每个cell要显示什么样的View呢?

那么这个时候内部的collectionView的DataSource就要外部去赋值了。所以我自定义了一个ContentCollectionView的DataSource方法。

可能你会想为啥不直接使用UICollectionView呢?因为直接使用UICollectionView就不好自定义代理方法了,继承的道理也一样,最好不要去破坏原生的控件。

1
2
3
4
5
6
protocol ContentCollectionViewDataSource: NSObjectProtocol {
// 有多少内容
func numberOfItems(in collectionView: UICollectionView) -> Int
// cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
}

那么这样我们就可以自定义不同的UICollectionViewCell去注册了。我们再开放两个register方法

1
2
3
4
5
6
7
8
9
/// 使用类注册Cell
func register(cellClass: AnyClass?, forCellWithReuseIdentifier: String) {
collectionView.register(cellClass, forCellWithReuseIdentifier: forCellWithReuseIdentifier)
}
/// 使用nib注册cell
func register(nib: UINib?, forCellWithReuseIdentifier: String) {
collectionView.register(nib, forCellWithReuseIdentifier: forCellWithReuseIdentifier)
}

然后我们使用的时候只需要将这个两个控件进行组合就行了,很爽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
private func setupView() {
self.automaticallyAdjustsScrollViewInsets = false
do {
headerView = HeaderView()
headerView.delegate = self
view.addSubview(headerView)
headerView.categories = categories
headerView.snp.makeConstraints({ (make) in
make.left.right.equalToSuperview()
make.height.equalTo(40)
make.top.equalTo(self.topLayoutGuide.snp.bottom)
})
}
do {
contentCollectionView = ContentCollectionView()
contentCollectionView.dataSource = self
contentCollectionView.delegate = self
contentCollectionView.register(cellClass: NewsContentCell.self, forCellWithReuseIdentifier: "cell")
view.addSubview(contentCollectionView)
contentCollectionView.snp.makeConstraints({ (make) in
make.top.equalTo(headerView.snp.bottom)
make.left.right.bottom.equalToSuperview()
})
}
}
}
extension NewsViewController: HeaderViewDelegate {
func categoryHeaderView(headerView: HeaderView, selectedIndex: Int) {
let indexPath = IndexPath(item: selectedIndex, section: 0)
contentCollectionView.scrollAffectToNoOnce()
contentCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false)
}
}
extension NewsViewController: ContentCollectionViewDelegate {
func contentCollectionView(_ contentView: ContentCollectionView, didShowViewWith index: Int) {
headerView.selectTitle(of: index)
}
func contentCollectionView(_ contentView: ContentCollectionView, didScrollFrom fromIndex: Int, to toIndex: Int, scale: Float) {
headerView.adjustTitle(from: fromIndex, to: toIndex, scale: scale)
}
}
extension NewsViewController: ContentCollectionViewDataSource {
func numberOfItems(in collectionView: UICollectionView) -> Int {
return categories.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? NewsContentCell
if let cell = cell {
cell.title = categories[indexPath.item]
return cell
}
cell = NewsContentCell()
cell!.title = categories[indexPath.item]
return cell!
}
}

好了,最近两篇文章没啥实质性的东西,UI层有很多需要深入的东西,只是自己也不是太深入,还需努力。

小伙伴们如果感觉文章可以,可以关注博主博客
小伙伴们多多关注博主微博,探索博主内心世界😁
如要转载请注明出处。

热评文章