前言:先看下效果
Tips:
- 这是用
Swfit
写的一个小Demo,用UICollectionView
实现的拖拽排序,点击排序的效果。- 我所用的
UICollectionView
的排序方法是系统默认的方法,优点是比较简单,不用自己去计算太多。缺点是只支持iOS 9.0
以后的版本。- 此Demo仅供参考,还有很多地方不完善,抽空我会再修改完善的,也欢迎各位给我提出缺点,并指正!
?用法简单介绍
ViewController
就是一个首页的普通控制器,当点击+
的时候,就会push
到频道管理(也就是标签列表)页面。- 在
ViewController
里自定义了两个数组,我的频道(myChannels
)和更多频道(moreChannels
) - 在点击
+
跳转到频道管理页面的点击方法里面有一个回调方法,即:将选中的频道、以及自定义后的频道回传到此页面。
var myChannels = ["推荐", "热点", "北京", "视频",
"社会", "娱乐", "问答", "汽车",
"财经", "军事", "体育", "段子",
"美女", "时尚", "国际", "趣图",
"健康", "特卖", "房产", "养生",
"历史", "育儿", "小说", "教育",
"搞笑"]
var moreChannels = ["科技", "直播", "数码", "美食",
"电影", "手机", "旅游", "股票",
"科学", "动漫", "故事", "收藏",
"精选", "语录", "星座", "美图",
"政务", "辟谣", "火山直播", "中国新唱将",
"彩票", "快乐男生", "正能量"]
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "王红庆"
view.backgroundColor = UIColor.white
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(popToChannelListViewController))
}
func popToChannelListViewController() -> () {
let channelVC = HQChannelListViewController(myChannel: myChannels, moreChannel: moreChannels)
channelVC.selectCallBack = { (myChannel, moreChannel, selectIndex) -> () in
self.navigationItem.title = myChannel[selectIndex]
self.myChannels = myChannel
self.moreChannels = moreChannel
}
navigationController?.pushViewController(channelVC, animated: true)
}复制代码
?所有的事情都交给HQChannelListViewController
来处理
- 首先定义一些可能用到的常量
private let SCREEN_WIDTH = UIScreen.main.bounds.size.width
private let SCREEN_HEIGHT = UIScreen.main.bounds.size.height
private let HQChannelListCellIdentifier = "HQChannelListCellIdentifier"
private let HQChannelListHeaderViewIdentifier = "HQChannelListHeaderViewIdentifier"
private let itemW: CGFloat = (SCREEN_WIDTH - 60) / 4复制代码
- 自定义流水布局,设置布局的一些属性
// MARK: - 自定义布局属性
class HQChannelListViewLayout: UICollectionViewFlowLayout {
override func prepare() {
super.prepare()
headerReferenceSize = CGSize(width: SCREEN_WIDTH, height: 40)
itemSize = CGSize(width: itemW, height: itemW * 0.5)
minimumInteritemSpacing = 5
minimumLineSpacing = 5
sectionInset = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20)
}
}复制代码
- 自定义
CollectionHeaderView
// MARK: - CollectionHeaderView
class HQChannelListHeaderView: UICollectionReusableView {
var editCallBack: (() -> ())?
var text: String? {
didSet {
label.text = text
}
}
func edit() -> () {
if editCallBack != nil {
editCallBack!()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupUI() {
addSubview(label)
addSubview(button)
backgroundColor = UIColor.groupTableViewBackground
}
private lazy var label: UILabel = {
let label = UILabel(frame: self.bounds)
label.frame.origin.x = 20
return label
}()
lazy var button: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("编辑", for: .normal)
btn.setTitle("完成", for: .selected)
btn.setTitleColor(UIColor.init(colorLiteralRed: 255 / 255.0, green: 0 / 255.0, blue: 0 / 255.0, alpha: 0.7), for: .normal)
btn.titleLabel?.font = UIFont.systemFont(ofSize: 13)
btn.frame = CGRect(x: SCREEN_WIDTH - 65, y: 10, width: 50, height: 25)
btn.addTarget(self, action: #selector(edit), for: .touchUpInside)
btn.layer.cornerRadius = 12.5
btn.layer.borderWidth = 1
btn.layer.borderColor = UIColor.init(colorLiteralRed: 255 / 255.0, green: 0 / 255.0, blue: 0 / 255.0, alpha: 0.7).cgColor
return btn
}()
}复制代码
- 自定义Cell
// MARK: - 自定义Cell
class HQChannelListCell: UICollectionViewCell {
var edit = true {
didSet {
imageView.isHidden = !edit
}
}
var text: String? {
didSet {
label.text = text
}
}
var textColor: UIColor = UIColor.darkGray {
didSet {
label.textColor = textColor
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.white
setupUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupUI() {
self.addSubview(label)
label.addSubview(imageView)
}
private lazy var label: UILabel = {
let label = UILabel(frame: self.bounds)
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 15)
return label
}()
private lazy var imageView: UIImageView = {
let imageView = UIImageView(frame: CGRect(x: self.bounds.size.width - 12, y: -3, width: 15, height: 15))
imageView.image = UIImage(named: "close")
imageView.isHidden = true
return imageView
}()
}复制代码
- 定义回调方法、给
Item
添加长按手势,并处理长按的一些状态(方法均为UICollectionView
提供的方法,只支持iOS 9.0
以后的版本)
class HQChannelListViewController: UIViewController {
// 选择一个频道后的回调
var selectCallBack: ((_ myChannel: [String], _ moreChannel: [String], _ selectIndex: Int) -> ())?
let headerTitle = [["我的频道", "更多频道"], ["拖动频道排序", "点击添加频道"]]
var array1 = ["推荐"]
var array2 = ["有声"]
var isEdit = false
init(myChannel: [String], moreChannel: [String]) {
array1 = myChannel
array2 = moreChannel
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "频道管理"
view.addSubview(collectionView)
}
// MARK: - longPress
func longPress(tap: UILongPressGestureRecognizer) -> () {
if !isEdit {
isEdit = !isEdit
collectionView.reloadData()
return
}
let point = tap.location(in: tap.view)
let sourceIndexPath = collectionView.indexPathForItem(at: point)
switch tap.state {
case UIGestureRecognizerState.began:
collectionView.beginInteractiveMovementForItem(at: sourceIndexPath!)
case UIGestureRecognizerState.changed:
collectionView.updateInteractiveMovementTargetPosition(point)
case UIGestureRecognizerState.ended:
collectionView.endInteractiveMovement()
case UIGestureRecognizerState.cancelled:
collectionView.cancelInteractiveMovement()
default:
break
}
}
// MARK: - lazy
private lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: HQChannelListViewLayout())
collectionView.backgroundColor = UIColor.groupTableViewBackground
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(HQChannelListCell.classForCoder(), forCellWithReuseIdentifier: HQChannelListCellIdentifier)
collectionView.register(HQChannelListHeaderView.classForCoder(), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: HQChannelListHeaderViewIdentifier)
let gesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress))
collectionView.addGestureRecognizer(gesture)
return collectionView
}()
}复制代码
- 实现
CollectionView
的数据源方法
// MARK: - UICollectionViewDataSource
extension HQChannelListViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return section == 0 ? array1.count : array2.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HQChannelListCellIdentifier, for: indexPath) as! HQChannelListCell
cell.text = indexPath.section == 0 ? array1[indexPath.item] : array2[indexPath.item]
cell.edit = (indexPath.section == 0 && indexPath.item == 0 || indexPath.section == 1) ? false : isEdit
if !isEdit {
cell.textColor = (indexPath.section == 0 && indexPath.item == 0) ? UIColor.init(colorLiteralRed: 255 / 255.0, green: 0 / 255.0, blue: 0 / 255.0, alpha: 0.7) : UIColor.darkGray
} else {
cell.textColor = (indexPath.section == 0 && indexPath.item == 0) ? UIColor.lightGray : UIColor.darkGray
}
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: HQChannelListHeaderViewIdentifier, for: indexPath) as! HQChannelListHeaderView
headerView.text = isEdit ? headerTitle[1][indexPath.section] : headerTitle[0][indexPath.section]
headerView.button.isSelected = isEdit
if indexPath.section > 0 {
headerView.button.isHidden = true
} else {
headerView.button.isHidden = false
}
headerView.editCallBack = { [weak self] in
self?.isEdit = !(self?.isEdit)!
collectionView.reloadData()
}
return headerView
}
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
// 设置第一组的第一个不能被移动
if indexPath.section == 0 && indexPath.item == 0 {
return false
}
return true
}
}复制代码
- 实现
CollectionView
的代理方法,在将选中的Item
移动到目标的Item
上的时候,我的方法处理的不是太好。但是想不到什么好法子,欢迎大家给我提思路,提建议。
// MARK: - UICollectionViewDelegate
extension HQChannelListViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.section == 0 {
if isEdit {
if indexPath.item == 0 {
return
}
let obj = array1[indexPath.item]
array1.remove(at: indexPath.item)
array2.insert(obj, at: 0)
collectionView.moveItem(at: indexPath, to: NSIndexPath(item: 0, section: 1) as IndexPath)
} else {
if selectCallBack != nil {
selectCallBack!(array1, array2, indexPath.item)
_ = navigationController?.popViewController(animated: true)
}
}
} else {
let obj = array2[indexPath.item]
array2.remove(at: indexPath.item)
array1.append(obj)
collectionView.moveItem(at: indexPath, to: NSIndexPath(item: array1.count - 1, section: 0) as IndexPath)
}
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
/*
1.以下方法是处理移动后的数组中的元素'删除'或'新增'问题.
2.不这样处理,就会崩溃.自己算法水平有限,也是真的没想到什么比较好的办法.
3.可能有人比较较真,提到如果真的像搜狐那么多'section'如何处理.个人感觉,目前市面上比较火的几家新闻,只有搜狐分的比较多,其它像'头条'或者'网易'也就都只有两组而已.
4.如果大家有什么好的方法,欢迎拍砖.我愿意像各位前辈学习.
*/
if sourceIndexPath.section == 0 && destinationIndexPath.section == 0 {
let obj = array1[sourceIndexPath.item]
array1.remove(at: sourceIndexPath.item)
array1.insert(obj, at: destinationIndexPath.item)
}
if sourceIndexPath.section == 0 && destinationIndexPath.section == 1 {
let obj = array1[sourceIndexPath.item]
array1.remove(at: sourceIndexPath.item)
array2.insert(obj, at: destinationIndexPath.item)
}
if sourceIndexPath.section == 1 && destinationIndexPath.section == 0 {
let obj = array2[sourceIndexPath.item]
array2.remove(at: sourceIndexPath.item)
array1.insert(obj, at: destinationIndexPath.item)
}
if sourceIndexPath.section == 1 && destinationIndexPath.section == 1 {
let obj = array2[sourceIndexPath.item]
array2.remove(at: sourceIndexPath.item)
array2.insert(obj, at: destinationIndexPath.item)
}
}
}复制代码
?总结
用Swift
造的第一个轮子,主要是给自己增加点积累,也练练Swift
的一些用法。
现在还存在的一些不尽人意的地方:
- 长按之后是变成编辑状态,不像《头条》或者《搜狐》那样长按之后变成编辑也可以继续拖动。
- 选中
Item
没有放大的效果,确实影响用户体验。 - 如果将
Item
从我的频道移动到更多频道里面,删除的x(小叉叉)
依然存在。 - 我的频道里面第一个
Item
本意上我是不希望他可以被移动的,但是如果将其它的Item
移动到第一个位置依然可以,背离了我的初衷。 - 仔细观察了一下,《头条》或者《搜狐》的更多频道里,如果将我的频道中的
Item
移动到更多频道里,《搜狐》只是放在更多频道里面的最后一个位置,《头条》是放在第一个的位置,并没有放哪里都行,我突然又感觉我自己的又有点多此一举了。看来有个好的产品经理还是很重要的。
以上是我个人的一些总结,我相信一定还有我自己没有注意到的地方存在问题。欢迎各位给我提宝贵意见。我会积极改正的!!!
DEMO传送门:HQChannelListView