美文网首页
UICollectionViewLayout

UICollectionViewLayout

作者: shuigenObba | 来源:发表于2017-08-10 15:12 被阅读27次

    UICollectionView 自定义简述

    写作目的

    UICollectionView是一个十分强大的控件,其所有的实现效果都是依赖于UICollectionViewLayout和UICollectionViewFlowLayout。我这里做一些简单的总结,希望能帮助大家,也是自己对知识的梳理。

    UICollectionViewLayout、UICollectionViewFlowLayout介绍

    由于系统自带的实现效果可能在真正的开发过程当中无法满足我们的需求,这时候我们就会想到自定义,而自定义UICollectionView其实就是自定义UICollectionViewLayout。

    UICollectionViewLayoutAttributes

    它的主要作用是负责存储每一个Cell位置、大小属性等。在一个CollectionView中可能有很多个这样的属性,我们也通过这个属性获取到每个cell存储的信息来进行布局

    open class UICollectionViewLayoutAttributes : NSObject, NSCopying, UIDynamicItem {
    
        
        open var frame: CGRect //获取到cell的frame
    
        open var center: CGPoint // 获取哒cell的center
    
        open var size: CGSize // size
    
        open var transform3D: CATransform3D // 设置动画
    
        @available(iOS 7.0, *)
        open var bounds: CGRect
    
        @available(iOS 7.0, *)
        open var transform: CGAffineTransform // 动画
    
        open var alpha: CGFloat
    
        open var zIndex: Int // default is 0
    
        open var isHidden: Bool // As an optimization, UICollectionView might not create a view for items whose hidden attribute is YES
    
        open var indexPath: IndexPath
    
        
        open var representedElementCategory: UICollectionElementCategory { get }
    
        open var representedElementKind: String? { get } // nil when representedElementCategory is UICollectionElementCategoryCell
    
        
        public convenience init(forCellWith indexPath: IndexPath)
    
        public convenience init(forSupplementaryViewOfKind elementKind: String, with indexPath: IndexPath)
    
        public convenience init(forDecorationViewOfKind decorationViewKind: String, with indexPath: IndexPath)
    }
    
    
    • UICollectionViewLayoutAttributes 的实例中包含了边框、中心、大小、形状、透明度、层次关系等信息
    • 一个cell对应一个UICollectionViewLayoutAttributes对象

    开始工作

    • 首先创建一个工程,并且 pod 需要的一些第三方库

    • 添加UI等

    • 自定义UICollectionViewCell

    
    class photoCell: UICollectionViewCell {
        
        var photoImageView: UIImageView?
        var photoImageS: String?{
            didSet{
                self.photoImageView?.image = UIImage(named: self.photoImageS!)
            }
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            
            photoImageView = UIImageView(frame: CGRect.zero)
            photoImageView?.layer.borderColor = UIColor.white.cgColor
            photoImageView?.layer.borderWidth = 10
            self.contentView.addSubview(self.photoImageView!)
            photoImageView?.snp.makeConstraints({ (make) in
                make.edges.equalTo(self.contentView)
            })
        }
        
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
    
    }
    
    
    class ViewController: UIViewController {
        
        var collectionView: UICollectionView!
        var dataSource: [String] = {
            var array: [String] = []
            for index in 0..<20 {
                array.append(String(index + 1))
            }
            return array
        }()
        let suqareLayout: SquareLayout = {
            let layout = SquareLayout()
            return layout
        }()
        
        let customFlow: ScrollFlowLayout = {
            let flowlayout = ScrollFlowLayout()
            flowlayout.itemSize = CGSize(width: 150, height: UIScreen.height / 2)
            return flowlayout
        }()
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
            
            initSubView()
            
            view.backgroundColor = UIColor.white
            
            
            let button = UIButton()
            button.frame = CGRect(x: 200, y: 300, width: 80, height: 80)
            button.setBackgroundImage(UIImage.init(named: "sub_add"), for: .normal)
            button.setBackgroundImage(UIImage.init(named: "sub_add_h"), for: .highlighted)
            button.addTarget(self, action: #selector(changeLayout), for: .touchUpInside)
            view.addSubview(button)
        }
        func changeLayout() {
            if self.collectionView.collectionViewLayout == suqareLayout {
                self.collectionView.setCollectionViewLayout(customFlow, animated: true)
            } else {
                self.collectionView.setCollectionViewLayout(suqareLayout, animated: true)
                
            }
        }
        
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    
    
        }
    }
    extension ViewController{
        // MARK:-- UI
        func initSubView(){
            collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: suqareLayout)
            collectionView.delegate = self
            collectionView.dataSource = self
            collectionView.backgroundColor = UIColor.clear
            collectionView.isScrollEnabled = true
            collectionView.showsVerticalScrollIndicator = false
            collectionView.showsHorizontalScrollIndicator = false
            collectionView.register(photoCell.self, forCellWithReuseIdentifier: NSStringFromClass(photoCell.self))
            view.addSubview(collectionView)
            
            collectionView.snp.makeConstraints { (make) in
                make.edges.equalTo(view)
            }
            
            
        }
    }
    
    extension ViewController: UICollectionViewDelegate {
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(photoCell.self), for: indexPath) as? photoCell
            cell?.photoImageS = self.dataSource[indexPath.row]
            return cell!
        }
        
        func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
            self.dataSource.remove(at: indexPath.row)
            self.collectionView.deleteItems(at: [indexPath])
        }
    }
    extension ViewController: UICollectionViewDataSource {
        func numberOfSections(in collectionView: UICollectionView) -> Int {
            return 1
        }
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return dataSource.count
        }
        
    }
    
    

    大家先不要看我自定义的UICollectionViewLayout,稍后我会给大家详细介绍。实现了UICollectionView的代理设置等。接下来就自定义UICollectionViewLayout

    自定义UICollectionViewLayout

    • 介绍layout的一些方法
    prepare()
            override func prepare() {
            super.prepare()
            AttribuArray.removeAll()
            let count = self.collectionView?.numberOfItems(inSection: 0)
            guard count != 0 else {
                print("没有数据")
                return
            }
            for index in 0..<Int(count!){
                let indexpath = NSIndexPath(item: index, section: 0)
                let attributes = self.layoutAttributesForItem(at: indexpath as IndexPath)
                self.AttribuArray.append(attributes!)
            }
         }
    
    

    当前方法的作用是初始化布局,获得所有cell的Attributes属性

    contensize

    <font color = #DC143C> 注意:这里的contentSize 是整个CollectionView的size,这个还要得益于UICollectionView继承与UIScrollView</font>

    // 设置ContentSize 这样才可以滑动
    override var collectionViewContentSize: CGSize{
        get{
            
            let count = self.collectionView?.numberOfItems(inSection: 0)
            let rows = (count! + 2) / 3
            let rowH: CGFloat = (self.collectionView?.width)! / 2
            if Int(rowH) * rows > Int(UIScreen.height) {
                return CGSize(width: 0, height: rowH * CGFloat(rows))
            } else {
                return CGSize(width: 0, height: rowH * CGFloat(rows) - rowH / 2)
            }
            
        } set{
            self.collectionViewContentSize = newValue
        }
    }
    
    设置展示样式 获得 Attributes

    <font color = #DC143C> 注意:
    layoutAttribsForItem(at indexpath: Indexpath) 和
    layoutAttributesInRect(In rect: CGRect)
    这两方法,前者返回的是indexpath的Attributes属性,而后者返回的是当前Rect中所有元素的布局,返回的是包含UICollectionViewLayoutAttris的Array
    </font>

        override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
            let width: CGFloat = (self.collectionView?.width)! * 0.5
            let attributes = UICollectionViewLayoutAttributes.init(forCellWith: indexPath)
            let height: CGFloat = width
            let i = indexPath.item
            
            switch i {
            case 0:
                attributes.frame = CGRect(x: 0, y: 0, width: width, height: height)
                break
            case 1:
                attributes.frame = CGRect(x: width, y: 0, width: width, height: height / 2)
                break
            case 2:
                attributes.frame = CGRect(x: width, y: height / 2, width: width, height: height / 2)
                break
            case 3:
                attributes.frame = CGRect(x: 0, y: height, width: width, height: height / 2)
                break
            case 4:
                attributes.frame = CGRect(x: 0, y: height + height / 2, width: width, height: height / 2)
                break
            case 5:
                attributes.frame = CGRect(x: width, y: height, width: width, height: height)
                break
       
            default:
                let last = self.AttribuArray[i - 6]
                var frame = last.frame
                frame.origin.y += 2 * height
                attributes.frame = frame
            }
            return attributes
        }
    
    
    
    • 赋上代码
    class SquareLayout: UICollectionViewLayout {
        
        // 存放相关的属性
        var AttribuArray : [UICollectionViewLayoutAttributes] = []
        override init() {
            super.init()
         
            
        }
        
        // 设置ContentSize 这样才可以滑动
        override var collectionViewContentSize: CGSize{
            get{
                
                let count = self.collectionView?.numberOfItems(inSection: 0)
                let rows = (count! + 2) / 3
                let rowH: CGFloat = (self.collectionView?.width)! / 2
                if Int(rowH) * rows > Int(UIScreen.height) {
                    return CGSize(width: 0, height: rowH * CGFloat(rows))
                } else {
                    return CGSize(width: 0, height: rowH * CGFloat(rows) - rowH / 2)
                }
                
            } set{
                self.collectionViewContentSize = newValue
            }
        }
        //初始化 布局
        override func prepare() {
            super.prepare()
            AttribuArray.removeAll()
            let count = self.collectionView?.numberOfItems(inSection: 0)
            guard count != 0 else {
                print("没有数据")
                return
            }
            for index in 0..<Int(count!){
                let indexpath = NSIndexPath(item: index, section: 0)
                let attributes = self.layoutAttributesForItem(at: indexpath as IndexPath)
                self.AttribuArray.append(attributes!)
            }
    
        }
        
        override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
            let width: CGFloat = (self.collectionView?.width)! * 0.5
            let attributes = UICollectionViewLayoutAttributes.init(forCellWith: indexPath)
            let height: CGFloat = width
            let i = indexPath.item
            
            switch i {
            case 0:
                attributes.frame = CGRect(x: 0, y: 0, width: width, height: height)
                break
            case 1:
                attributes.frame = CGRect(x: width, y: 0, width: width, height: height / 2)
                break
            case 2:
                attributes.frame = CGRect(x: width, y: height / 2, width: width, height: height / 2)
                break
            case 3:
                attributes.frame = CGRect(x: 0, y: height, width: width, height: height / 2)
                break
            case 4:
                attributes.frame = CGRect(x: 0, y: height + height / 2, width: width, height: height / 2)
                break
            case 5:
                attributes.frame = CGRect(x: width, y: height, width: width, height: height)
                break
       
            default:
                let last = self.AttribuArray[i - 6]
                var frame = last.frame
                frame.origin.y += 2 * height
                attributes.frame = frame
            }
            return attributes
        }
        
        override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            
            return AttribuArray
        }
        
        
        
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    
    import UIKit
    import Foundation
    class ScrollFlowLayout: UICollectionViewFlowLayout {
        
        
        /*
         注意: 在初始化一个UICollectionViewLayout实例吼,会有一系列准备方法被自动调用,保证layout实例的正确
         1.prepare
         2.CollectionViewContentSize
         3.layoutAttributesForElementInRect -> 初始化的layout的外观将由该方法返回的UICollectionViewLayoutAttributes决定
         */
        
        override init() {
            super.init()
            
            self.minimumLineSpacing = 0.7 * self.itemSize.width
        }
        
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        // MARK:-- 特别注意,布局的初始化操作,不要在init中做布局的初始化操作
        override func prepare() {
            super.prepare()
            
            self.scrollDirection = .horizontal
            let insert: CGFloat = ((self.collectionView?.width)! - self.itemSize.width) / 2
            self.sectionInset = UIEdgeInsetsMake(0, insert, 0, insert)
        }
        
        
        override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            
            let array = super.layoutAttributesForElements(in: rect)
            
            let centerX = (self.collectionView?.contentOffset.x)! + (self.collectionView?.width)! / 2
            
    //        print("layoutAttributesForElementsInRect==\(rect)=======\(centerX)")
            for attribute in array! {
    //            print("第 \(attribute.indexPath.item) cell --距离:\(attribute.center.x - centerX)")
                
                let delta = abs(attribute.center.x - centerX);
                let scale = 1 - delta/(self.collectionView?.width)!
                attribute.transform = CGAffineTransform(scaleX: scale, y: scale)
            }
            
            return array!
        }
        
        override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
            return true
        }
       
        override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
            
            var rect = CGRect()
            rect.origin.x = proposedContentOffset.x
            rect.origin.y = proposedContentOffset.y
            rect.size = (self.collectionView?.frame.size)!
            
            let array = super.layoutAttributesForElements(in: rect)
            let centerX = (self.collectionView?.width)! / 2 + proposedContentOffset.x
            print("=====\(proposedContentOffset.x)")
            //存放的最小间距
            //TODO:   注意研究一下:
            var minDelta = MAXFLOAT
            for attribute in array! {
                if Float(abs(minDelta)) > Float(abs(attribute.center.x - centerX)) {
                    minDelta = Float(attribute.center.x - centerX)
                }
            }
            // 修改原有的偏移量
    
            //如果返回的时zero 那个滑动停止后 就会立刻回到原地
            return CGPoint.init(x: CGFloat(proposedContentOffset.x + CGFloat(minDelta)), y: proposedContentOffset.y)
            
        }
        
        
    }
    
    

    <font color = #DC143C> 方法调用的先后顺序

    1.prepare()

    2.collectionViewContentSize

    3.layoutAttributesFoeItem

    4.layoutAttributesInRect

    相关文章

      网友评论

          本文标题:UICollectionViewLayout

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