Shcg


UICollectionView 固定行距列表左排: 来一个自定制 Layout

一般我们是使用 UICollectionViewFlowLayout , 熟悉的格子视图。也可以自定制 UICollectionViewLayout ,对于每一个列表元素,想放哪就放哪。

譬如: 固定行距列表左排

00.png

这种情况,系统的就不好直接拿来使了,需要自己定制一个 UICollectionViewLayout.

一般 new 一个 UICollectionViewLeftAlignedLayout, 继承自 UICollectionViewFlowLayout

通常要重写 UICollectionViewFlowLayout 的这两个方法,

layoutAttributesForElements(in:), 这个方法需要提供,给定的矩形里面所有的格子的布局属性。给定的矩形区域,就是 UICollectioonView 的内容视图区域 contentSize.

layoutAttributesForItem(at:): 这个方法需要提供,格子视图需要的具体的布局信息。我们要重写这个方法,返回要求的 indexPath 位置上格子的布局属性。

有时候也要重写这个属性:

collectionViewContentSize, 一般我们是把内容区域的尺寸,作为计算属性处理的。他提供格子视图的内容区域的宽度与高度。格子视图的内容区域,不是格子视图的可见区域。

因为格子视图 UICollectionView,继承自 UIScrollView。 格子视图使用该属性,配置他作为可滑动视图 UIScrollView 的内容视图尺寸。

主要代码见如下:

其中辅助函数没有列出来,具体见文尾的 github repo.

class UICollectionViewLeftAlignedLayout: UICollectionViewFlowLayout {

// 这个函数没有做什么事情,主要是调用做事情的函数 layoutAttributesForItem,获取信息,提供出去
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        var attributesCopy: [UICollectionViewLayoutAttributes] = []
        if let attributes = super.layoutAttributesForElements(in: rect) {
            attributes.forEach({ attributesCopy.append($0.copy() as! UICollectionViewLayoutAttributes) })
        }
        for attributes in attributesCopy {
            if attributes.representedElementKind == nil {
                let indexpath = attributes.indexPath
                // 做事情的地方
                if let attr = layoutAttributesForItem(at: indexpath) {
                    attributes.frame = attr.frame
                }
            }
        }
        return attributesCopy
    }
    
// 这个函数里面,具体处理了固定行距列表左排的布局
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        
        if let currentItemAttributes = super.layoutAttributesForItem(at: indexPath as IndexPath)?.copy() as? UICollectionViewLayoutAttributes, let collection = collectionView {
            let sectionInset = evaluatedSectionInsetForItem(at: indexPath.section)
            let isFirstItemInSection = indexPath.item == 0
            let layoutWidth = collection.frame.width - sectionInset.left - sectionInset.right
            // 让每一行的第一个元素排头,分两种情况处理。这是第一种,这个 section 的第一个元素,自然是排头。
            guard !isFirstItemInSection else{
                currentItemAttributes.leftAlignFrame(with: sectionInset)
                return currentItemAttributes
            }
            let previousIndexPath = IndexPath(item: indexPath.item - 1, section: indexPath.section)
            let previousFrame = layoutAttributesForItem(at: previousIndexPath)?.frame ?? CGRect.zero
            let previousFrameRightPoint = previousFrame.origin.x + previousFrame.width
            let currentFrame = currentItemAttributes.frame
            let strecthedCurrentFrame = CGRect(x: sectionInset.left,
                                                    y: currentFrame.origin.y,
                                                    width: layoutWidth,
                                                    height: currentFrame.size.height)
            let isFirstItemInRow = !previousFrame.intersects(strecthedCurrentFrame)
            // 让每一行的第一个元素排头,分两种情况处理。这是第二种,这个 section 的其他的排头,算出来,就是:上一个格子在上一行,不在当前行,
            guard !isFirstItemInRow else{
                currentItemAttributes.leftAlignFrame(with: sectionInset)
                return currentItemAttributes
            }
            //  剩下的,简单了。统一处理掉。 剩下的格子都不是排头,与上一个固定间距完了。
            var frame = currentItemAttributes.frame
            frame.origin.x = previousFrameRightPoint + evaluatedMinimumInteritemSpacing(at: indexPath.section)
            currentItemAttributes.frame = frame
            return currentItemAttributes
            
        }
        return nil
    }
// ...
}

说一下 func layoutAttributesForItem(at indexPath: IndexPath) 的设计思路。

因为如果使用 UICollectionViewFlowLayout ,什么都不干,与上图的区别就一点。
每一行的元素个数一致,具体也一致,就是那些格子是居中的。毕竟 minimumInteritemSpacing 是最小行内间距的意思,不是固定的行内间距。

然后移一移,就好了。让每一行的第一个元素排头,每一行的其他元素与上一个元素固定间距,这就完了。

例子二: 固定行距列表右排

11.png

分析:看起来,右排就是把左边排列好的元素,推往右边。具体就是左边排列好的元素,每一行的元素都添加一个当前行的 OffsetX,

OffsetX = CollectionView 的 frame.width - 左边排列好元素的最后一个 frame.maxX

这是另外一种情况。因为不能改一改左排的 layoutAttributesForItem 方法,就好。左排用的是 previous,没什么问题。

右排用 next , 对于每一个元素,找 next , 知道其 X + width > collectionView 的 width, OffsetX 就出来了。

采用 layoutAttributesForItem 找 next, 会有一个比较烦的递归,当前找 next, next 找 next.
其实第一种情况也是这样,当前找 previous, previous 找 previous.

区别在于苹果做了优化,next 找 next,因为我在 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 返回的尺寸宽度随机。

然后就乱套了。当前找 next 返回的随机的 size, 到了 next, 会返回另外随机的 size.
previous 找 previous, 就没有这方面的问题。

我采用的是算两遍,先左排,再基于左排的结果,添加 OffsetX , 就变右排了。

// 我用了锁,测试时,每秒刷新一个布局。次数多了,会出现 Mac 上多核心 CPU 异步绘制的结果不太好

let lock = NSLock()
// 因为要算两次,第一次就不能放在  func layoutAttributesForElements(in rect: CGRect)  方法,
// 我放在  func prepare() 方法,采用创建的 UICollectionViewLayoutAttributes
    override func prepare() {
        lock.lock()
        contentHeight = 0
        cache.removeAll()
        storedCellSize.removeAll()
        guard let collectionView = collectionView else {
            return
        }
        var currentXOffset:CGFloat = 0
        var nextXOffset:CGFloat = 0
        var currentYOffset:CGFloat = 0
        var nextYOffset:CGFloat = 0
        
        
        for section in 0..<collectionView.numberOfSections{
            let sectionInset = evaluatedSectionInsetForItem(at: section)
            nextXOffset = sectionInset.left
            nextYOffset += sectionInset.top
            let count = collectionView.numberOfItems(inSection: section)
            for item in 0..<count{
                currentXOffset = nextXOffset
                currentYOffset = nextYOffset
                let indexPath = IndexPath(item: item, section: section)
                // 采用创建的 UICollectionViewLayoutAttributes,不是访问系统的 super.layoutAttributesForItem(at:)
                let currentItemAttributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
                let currentIndexPathSize = queryItemSize(indexPath)
                let currentItemInNext = (currentXOffset + evaluatedMinimumInteritemSpacing(at: section) + currentIndexPathSize.width) > (collectionView.frame.width - sectionInset.right + 0.1)
                if currentItemInNext{
                    currentXOffset = sectionInset.left
                    currentYOffset += (currentIndexPathSize.height + evaluatedMinimumLineSpacing(at: section))
                    
                    nextXOffset = currentXOffset + (currentIndexPathSize.width + evaluatedMinimumInteritemSpacing(at: section))
                   
                    nextYOffset = currentYOffset
                }else{
                    nextXOffset += (currentIndexPathSize.width + evaluatedMinimumInteritemSpacing(at: section))
                }
                
                
                let frame = CGRect(origin: CGPoint(x: currentXOffset, y: currentYOffset), size: currentIndexPathSize)
                currentItemAttributes.frame = frame
                cache[indexPath] = currentItemAttributes
                contentHeight = max(contentHeight, frame.maxY)
            }
            nextYOffset = contentHeight
        }
        lock.unlock()
    }
    
    // 这个函数没有做什么事情,主要是调用做事情的函数 layoutAttributesForItem,获取信息,提供出去
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        
        var attributesCopy: [UICollectionViewLayoutAttributes] = []
        if let attributes = super.layoutAttributesForElements(in: rect) {
            attributes.forEach({ attributesCopy.append($0.copy() as! UICollectionViewLayoutAttributes) })
        }
        for attributes in attributesCopy {
            if attributes.representedElementKind == nil {
                let indexpath = attributes.indexPath
                if let attr = layoutAttributesForItem(at: indexpath) {
                    attributes.frame = attr.frame
                }
            }
        }
        return attributesCopy
    }
    
    
    // 这个函数是第二次计算。把第一计算的结果左排,通过算出 OffsetX 添加上去,变成右排
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let collectionView = collectionView,let attribute = cache[indexPath] else {
            return nil
        }
        
        var offsetX: CGFloat = attribute.frame.maxX
        let criteria = collectionView.frame.width - evaluatedSectionInsetForItem(at: indexPath.section).right - 0.1
        var gap = criteria - offsetX
        var ip = indexPath
        let sectionCount = collectionView.numberOfItems(inSection: indexPath.section)
        while ip.item < sectionCount{
           // 通过 Y 值来比较,结果比较稳定。同一行嘛,Y 坐标,自然是一致的。
            var conditionSecond = false
            if let nextAttri = cache[ip.next]{
                conditionSecond = nextAttri.frame.minY != attribute.frame.minY
            }
            if (ip.item + 1) >= sectionCount || conditionSecond {
                gap = criteria - offsetX
                break
            }
            else{
                ip = ip.next
                offsetX += (evaluatedMinimumInteritemSpacing(at: indexPath.section) + cache[ip]!.frame.width)
               
            }
        }
        attribute.trailingAlignFrame(with: gap)
        return attribute
        
    }

这里用到了 prepare() 方法,当有布局操作的时候,就调用这个方法。
我们用这个时机,计算出提供给 collectionView 的尺寸和这些格子的位置。

: 例子 3 , 右边反着排

22.png

同例子一,把每行第一个元素,铺到最右端,其余的格子保持固定间距就好了。

代码如下:

// 这个函数没有做什么事情,主要是调用做事情的函数 layoutAttributesForItem,获取信息,提供出去
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        
        var attributesCopy: [UICollectionViewLayoutAttributes] = []
        if let attributes = super.layoutAttributesForElements(in: rect) {
            attributes.forEach({ attributesCopy.append($0.copy() as! UICollectionViewLayoutAttributes) })
        }
        
        for attributes in attributesCopy {
            if attributes.representedElementKind == nil {
                let indexpath = attributes.indexPath
                if let attr = layoutAttributesForItem(at: indexpath) {
                    attributes.frame = attr.frame
                }
            }
        }
        return attributesCopy
    }
    
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        
        if let currentItemAttributes = super.layoutAttributesForItem(at: indexPath as IndexPath)?.copy() as? UICollectionViewLayoutAttributes , let collection = collectionView{
            let isFirstItemInSection = indexPath.item == 0
 
            // 右边派头情况一
            if isFirstItemInSection {
                currentItemAttributes.rightAlignFrame(with: collection.frame.size.width)
                return currentItemAttributes
            }
            
            let previousIndexPath = IndexPath(item: indexPath.item - 1, section: indexPath.section)
            
            let previousFrame = layoutAttributesForItem(at: previousIndexPath)?.frame ?? CGRect.zero
  
            let currentFrame = currentItemAttributes.frame
            let strecthedCurrentFrame = CGRect(x: 0,
                                                    y: currentFrame.origin.y,
                                                    width: collection.frame.size.width,
                                                    height: currentFrame.size.height)
            let isFirstItemInRow = !previousFrame.intersects(strecthedCurrentFrame)
             // 右边派头情况二
            if isFirstItemInRow {
                currentItemAttributes.rightAlignFrame(with: collection.frame.size.width)
                return currentItemAttributes
            }
             // 右边正常的情况
            let previousFrameLeftPoint = previousFrame.origin.x
            var frame = currentItemAttributes.frame
            let minimumInteritemSpacing = evaluatedMinimumInteritemSpacing(at: indexPath.item)
            frame.origin.x = previousFrameLeftPoint - minimumInteritemSpacing - frame.size.width
            currentItemAttributes.frame = frame
            return currentItemAttributes
            
        }
        return nil
    }

BoxDengJZ/UICollectionViewLeftAlignedLayout

StackOverFlow: How do you determine spacing between cells in UICollectionView flowLayout



认识 RxSwift: 方法调用时机的替换 RxDataSource ...



Live Photo : 实况照片的拍摄、查看和编辑


实况照片的拍摄



实况照片的查看


实况照片的编辑























查看 分为预览 和 播放




























与视频播放添加覆盖物和动画














































AVFoundation 偏方进阶:合成 instruction.

































































空灵澄静:
把一切 都忘掉
的片刻 是 好的,舒服的。
没有什么 需要 每时每刻 揉进生命中,那都是 错觉。

有什么 可以 忘记呢?
触发即生效,触发即回忆起来。
体验,非常美妙。
相信 我的身体



类方法 调用,无属性。
脱离 实例对象。

实例对象,有自己的生命周期和 状态。



生产力的提高:
Java 的 IDEAItell 肯定比 Eclipse 好
Go 肯定比 Java 好。



基础架构组,
基础服务组

提供业务调用日志,统计数据
压测,性能优化,API 与数据库调用。找bug



今天 给 任亚彬 讲 干货,

他想 转 后台

他说
他是机器学习 菜鸟 级别。
想要 找队友。

收费 ¥1



20171223

美团

基础服务组/基础架构组

提供数据/日志

压测 , 找 bug





感觉我可能不太适合贵公司,我提出一下,请贵公司考虑,以免耽误贵公司的宏伟发展。
如果要换人,请结算工资,我工作交接一些零碎。12K * 0.8 * 三周 * 扣点税 * 看着扣,保证价格公道,就好了。
如果不,请按时发放工资,提供正规的合同。 孙成说:避税虽好,坐蜡不好。

我对创格有很多疑惑,

1、 听说老板对IT部很重视,为什么三个星期了,也没见见老板。
然后传说老板各种着急,怎么从不见他来这边了解情况,指导工作?
小孙觉得,不符合常理。
招人、谈薪资,是CTO一口定的,老板也不过目一下。这是一种重视吗?
也从来没有什么,老板请大家吃个饭什么的,认识一下,交流下感情。
工作环境偏恶劣,昏昏暗暗的,吴先望感觉像是临时工。
搬到新的办公点,小孙经常说: 她不喜欢吹牛逼。

2、 技术管理?
我说后台和UI (好像大)有问题,CTO看起来挺震惊的,不像是作假。
后台写的接口,逻辑上当然都说得通,看起来别扭,像小学生写的。
我当然可以列个文档,慢慢改进。
我又不是总监,管这个,管那个。
我指出问题,给自己争取点时间就可以了。
拿一分钱,办一分事。

感觉挺热衷项目上线融资的。
反正期望挺大的,万一落空。

产品的迷之自信,可以分享下。

3、钱的问题?
钱由合同保障。
合同、合同上的金额、工资发放时间,
吴先望经常表示呵呵。
吴先望说: 合同,什么样的律师会精通违反劳动法。什么样的人,才会找侵害劳动者合法权益的律师。
孙成强烈建议: 公司要是拒绝给合同,就到隔壁办公室抱一摞文件,说不定能换两钱。
我是个好人,当然不会这样做。
颗粒无收,是不好的。
现在解决了一些,明天看看去。

  1. 产品君?
    尊敬的产品经理,觉得我技术不行,
    那就早点强烈建议把我开了呗,把工钱结了。
    不要耽误大家的时间。

小孙又感觉,不太科学。

,有充分的时间把我的事情做好
CTO的
给我
//后台当然都写好了。时不时要发布一下,耽误个把小时,反正一天有9小时长啊。
见字如面啊。

工程师拿钱,当然办事。
项目成败 ,荣耀或杯具,归公司,归领导。
�不知道你每天收集下午的工作进度?可有什么大的收获?
最重要的是

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 连续三年自由行都去了海边旅游,每次旅游前都花费大量的时间做攻略,提前几个月就从网上收集大量的...
    D032大灰狼_佛山阅读 333评论 3 9
  • 有人推荐《致命ID》这部电影,说结局出乎意料。周五晚上,我看了这部电影。这部电影属于悬疑+惊悚的题材。很多电影,我...
    矮纸斜行闲作文阅读 252评论 2 3
  • 暗恋有多辛苦,就好像在商店里看到一个喜欢的玩具,喜欢到眼里只有它。但是自己的钱不够,于是拼命的存钱,当自己存够了以...
    弦断手心阅读 159评论 0 1
  • 三月里的江南 总是烟雨茫茫 在这草长莺飞的季节 寻你在烟波浩渺的西湖里 寻你在乌蓬摇撸的桨声里 寻你在周庄温柔怀抱...
    想飞的鱼521314阅读 152评论 0 0