本文基于IGListKit 4.0实现列表的高帧率滑动效果,项目地址见GitHub

话不多说,上图

moments.webp.gif

创建基类控制器

所有IGListKit的视图控制器都应该继承此类,减少复用

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
class BaseListVC: UIViewController {

var objects: [ListDiffable] = [ListDiffable]()

lazy var collectionView: UICollectionView = {
let flow = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: flow)
if #available(iOS 11.0, *) {
collectionView.contentInsetAdjustmentBehavior = .never
} else {
automaticallyAdjustsScrollViewInsets = false
}
collectionView.backgroundColor = UIColor.groupTableViewBackground
return collectionView
}()
lazy var adapter: ListAdapter = {
let adapter = ListAdapter(updater: ListAdapterUpdater(), viewController: self)
return adapter
}()


override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
adapter.collectionView = collectionView
adapter.dataSource = self
}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
}

extension BaseListVC : ListAdapterDataSource {
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
return objects
}

func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
return ListSectionController()
}

func emptyView(for listAdapter: ListAdapter) -> UIView? {
// 无数据时collectionView的展示
return nil
}
}

创建MomentInfo模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MomentInfo {
var id: Int = 0
...
}
extension MomentInfo: ListDiffable {
func diffIdentifier() -> NSObjectProtocol {
// 区分是否为同一对象,可多属性叠加
return id as NSObjectProtocol
}

// 判断对象是否相同,不同(false)则刷新
func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
guard self === object else { return true }
guard let object = object as? MomentInfo else { return false }
return id == object.id
}
}

extension MomentInfo: Equatable {
static func == (lhs: MomentInfo, rhs: MomentInfo) -> Bool {
return lhs.isEqual(toDiffableObject: rhs)
}
}

子类复写

  1. 更新数据
1
2
3
4
//info -> MomentInfo的对象
self.objects.append(info)
// 数据添加完成后更新
self.adapter.performUpdates(animated: true, completion: nil)
  1. 复写绑定Section
1
2
3
4
5
6
7
8
9
10
11
override func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
// 此object的类型就是更新的self.objects的item类型
switch object {
case is MomentInfo:
// MomentBindingSection 继承自 ListBindingSectionController
let section = MomentBindingSection()
return section
default:
fatalError()
}
}

使用ListBindingSectionController绑定多个cell

  1. 创建UICollectionViewCell⚠️只能有一种样式布局,不同样式需要不同的cell
1
2
3
4
5
6
7
8
9
class MomentTopCell: UICollectionViewCell {
...
}
extension MomentTopCell: ListBindable {
func bindViewModel(_ viewModel: Any) {
guard let viewModel = viewModel as? MomentInfo else { return }
// 更新cell数据
}
}
  1. 获取cell对象
1
2
3
4
5
6
7
func momentTopCell(at index: Int) -> MomentTopCell {
// guard let cell = collectionContext?.dequeueReusableCell(withNibName: MomentTopCell.jx_className, bundle: nil, for: self, at: index) as? MomentTopCell else { fatalError() }
guard let cell = collectionContext?.dequeueReusableCell(of: MomentTopCell.self, for: self, at: index) as? MomentTopCell else { fatalError() }
// 传入cell数据
cell.bindViewModel(object!)
return cell
}
  1. 绑定视图模型(ViewModel),可使用枚举替代
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
enum ViewModelEnum: String {
case top, header, image_single, location, bottom
}
/// 绑定viewmodels,用以区分不同cell
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, viewModelsFor object: Any) -> [ListDiffable] {
guard let object = object as? MomentInfo else { return [] }
var results: [ListDiffable] = []
if object.userInfo != nil {
results.append(ViewModelEnum.top.rawValue as ListDiffable)
}
if object.images.count == 1 {
results.append(ViewModelEnum.image_single.rawValue as ListDiffable)
}else {
results.append(ViewModelEnum.header.rawValue as ListDiffable)
}
if !object.location.isEmpty {
results.append(ViewModelEnum.location.rawValue as ListDiffable)
}
results.append(ViewModelEnum.bottom.rawValue as ListDiffable)
return results
}

/// 不同viewmodel对应的cell
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, cellForViewModel viewModel: Any, at index: Int) -> UICollectionViewCell & ListBindable {
let viewModel = ViewModelEnum(rawValue: viewModel as! String)!
switch viewModel {
case .top:
return momentTopCell(at: index)
case .image_single:
return momentHeaderImageCell(at: index)
case .header:
return momentHeaderCell(at: index)
case .location:
return momentLocationCell(at: index)
case .bottom:
return momentBottomCell(at: index)
}
}
/// 不同cell对应的size
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, sizeForViewModel viewModel: Any, at index: Int) -> CGSize {
guard let object = object as? MomentInfo else { fatalError() }
let viewModel = ViewModelEnum(rawValue: viewModel as! String)!
let width: CGFloat = collectionContext!.containerSize(for: self).width
switch viewModel {
case .top:
return CGSize(width: width, height: 400)
case .header, .image_single:
return CGSize(width: width, height: object.cellHeight)
case .location:
return CGSize(width: width, height: 30)
case .bottom:
return CGSize(width: width, height: 30)
}
}