美文网首页iOS-swiftSwift开发实战iOS开发
Swift-低仿搜狐新闻标签页效果

Swift-低仿搜狐新闻标签页效果

作者: 一位不愿透露姓名的王先生_ | 来源:发表于2017-06-04 18:26 被阅读354次

    前言:先看下效果

    Tips:

    1. 这是用Swfit写的一个小Demo,用UICollectionView实现的拖拽排序,点击排序的效果。
    2. 我所用的UICollectionView的排序方法是系统默认的方法,优点是比较简单,不用自己去计算太多。缺点是只支持iOS 9.0以后的版本。
    3. 此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的一些用法。
    现在还存在的一些不尽人意的地方:

    1. 长按之后是变成编辑状态,不像《头条》或者《搜狐》那样长按之后变成编辑也可以继续拖动。
    2. 选中Item没有放大的效果,确实影响用户体验。
    3. 如果将Item我的频道移动到更多频道里面,删除的x(小叉叉)依然存在。
    4. 我的频道里面第一个Item本意上我是不希望他可以被移动的,但是如果将其它的Item移动到第一个位置依然可以,背离了我的初衷。
    5. 仔细观察了一下,《头条》或者《搜狐》的更多频道里,如果将我的频道中的Item移动到更多频道里,《搜狐》只是放在更多频道里面的最后一个位置,《头条》是放在第一个的位置,并没有放哪里都行,我突然又感觉我自己的又有点多此一举了。看来有个好的产品经理还是很重要的。

    以上是我个人的一些总结,我相信一定还有我自己没有注意到的地方存在问题。欢迎各位给我提宝贵意见。我会积极改正的!!!

    DEMO传送门:HQChannelListView

    相关文章

      网友评论

        本文标题:Swift-低仿搜狐新闻标签页效果

        本文链接:https://www.haomeiwen.com/subject/cypufxtx.html