前言:先看下效果
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