【问题标题】:UICollectionViewFlowLayout Place Section Headers over Section Left InsetUICollectionViewFlowLayout 将部分标题放在部分左插图上
【发布时间】:2015-10-07 11:06:42
【问题描述】:

我正在尝试修改UICollectionViewFlowLayout(垂直滚动),以便将每个节标题放在该节所有项目的左侧(而不是在顶部,这是默认设置)。

也就是说,这是默认行为:

...这就是我想要的:

所以我继承了UICollectionViewFlowLayout

override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    guard let attributesToReturn = super.layoutAttributesForElementsInRect(rect) else {
        return nil
    }

    // Copy to avoid the dreadded "Cached frame mismatch" runtime warning:
    var copiedAttributes = [UICollectionViewLayoutAttributes]()

    for attribute in attributesToReturn {
        copiedAttributes.append(attribute.copy() as! UICollectionViewLayoutAttributes)
    }

    for attributes in copiedAttributes {
        if let kind = attributes.representedElementKind {
            // Non nil: It is a supplementary View

            if kind == UICollectionElementKindSectionHeader {
                // HEADER

                var frame = attributes.frame
                frame.origin.y = frame.origin.y + frame.size.height
                frame.size.width = sectionInset.left
                attributes.frame = frame     
            }
        }
        else{
            // Nil: It is an item
        }
    }
    return copiedAttributes
}

另外,为了更好的措施(?),我采用了协议UICollectionViewDelegateFlowLayout并实现了这个方法(虽然不清楚什么优先。然后,故事板文件中有设置,但是那些似乎被他们的运行时对应物覆盖)

func collectionView(
    collectionView: UICollectionView,
    layout collectionViewLayout: UICollectionViewLayout,
    referenceSizeForHeaderInSection section: Int) -> CGSize {

    let left = (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset.left

    return CGSizeMake(left, 1)
}

...我成功地将标题降低到其部分的第一行;但是,最初由 header 占用的空间保持打开状态:

...我能做到这一点的唯一方法是将标题视图高度设置为1 并将“剪辑子视图”设置为 false,以便显示标签(如果我将高度设置为 0,则标签未绘制),但这绝对是不是最优雅的解决方案(很可能会中断-说-iOS 9.2

也就是说,标题的实际高度链接到部分之间的垂直空间:我不能将空间设置为零,同时将标题视图保持在合理的大小以进行显示。

也许我也应该向上移动所有部分项目(与我的标题高度相同的数量)来填补这个洞?

【问题讨论】:

  • 你发现了吗?我正在尝试做同样的事情,但对于水平滚动集合视图
  • 其实是的。我一直忙于发布答案,但我今天会做。给我几个小时!
  • 太棒了!迫不及待想听

标签: ios swift uicollectionview


【解决方案1】:
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray *originalAttributes = [super layoutAttributesForElementsInRect:rect];
    NSMutableArray *allAttributes = [NSMutableArray new];
    for (UICollectionViewLayoutAttributes* attributes in originalAttributes) {
        [allAttributes addObject:[attributes copy]];
    }

    for (UICollectionViewLayoutAttributes *attributes in allAttributes) {
         NSString *kind = attributes.representedElementKind;
         if (kind == UICollectionElementKindSectionHeader) {
            CGRect frame = attributes.frame;
            UICollectionViewLayoutAttributes *cellAttrs = [super layoutAttributesForItemAtIndexPath:attributes.indexPath];
            frame.origin.x = frame.origin.x;
            frame.size.height = self.sectionInset.top;
            frame.size.width = cellAttrs.frame.size.width;
            attributes.frame = frame;
         }
    }

    return allAttributes;
}

【讨论】:

  • 看起来你已经得到了正确的解决方案的基本部分,而不是我冗长的、完全笨拙的答案。我没有时间检查仅此代码是否会产生预期结果,但请告诉我。如果是这样,您可能使我的 github 演示项目实施变得更加容易。
  • 乍一看,您似乎在复制属性数组 (allAttributes),而不是子数组本身 (attributes);即浅拷贝。您没有收到运行时警告吗?
  • 您的代码有效,我接受您的回答。但是,我可以确认,如果您不深度复制属性,您会在运行时收到此警告(但布局不会中断):
  • Logging only once for UICollectionViewFlowLayout cache mismatched frame 2015-10-16 12:25:05.283 CustomFlowLayoutDemo[15159:1753244] UICollectionViewFlowLayout has cached frame mismatch for index path &lt;NSIndexPath: 0xc000000000200016&gt; {length = 2, path = 0 - 1} - cached value: {{386, 1}, {226, 89}}; expected value: {{486, 1}, {226, 89}} 2015-10-16 12:25:05.283 CustomFlowLayoutDemo[15159:1753244] This is likely occurring because the flow layout subclass CustomFlowLayoutDemo.LeftAlignedFlowLayout is modifying attributes returned by UICollectionViewFlowLayout without copying them
  • 在 Objective-C 中,我认为你应该这样做:NSMutableArray *originalAttributes = [super layoutAttributesForElementsInRect:rect]; NSMutableArray *allAttributes = [NSMutableArray new]; for (UICollectionViewLayoutAttributes* attributes in originalAttributes){ [allAttributes addObject:[attributes copy]]; }
【解决方案2】:

好的,这就是我所做的:

首先,我基于this github projectUICollectionViewFlowLayout 进行了子类化,以获得我正在寻找的左对齐(我必须将它从Objective-C 转换为swift,但除此之外几乎相同) .

由于我已经对布局对象进行了子类化,我可以在这个子类中实现任何修改。

我声明了一个属性来存储我的“侧页眉”的宽度(这个数量也是每个部分的左插图的两倍):

import UIKit

class LeftAlignedFlowLayout: UICollectionViewFlowLayout
{
    let customHeaderWidth:CGFloat = 150.0

    required init?(coder aDecoder: NSCoder) {

        super.init(coder: aDecoder)        
        sectionInset.left = customHeaderWidth
    }

然后,在layoutAttributesForElementsInRect()的实现中我这样做了:

override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]?
{
    guard let attributesToReturn = super.layoutAttributesForElementsInRect(rect) else {
        return nil
    }

    var copiedAttributes = [UICollectionViewLayoutAttributes]()

    for attribute in attributesToReturn {
        // Must copy attributes to avoid runtime warning:

        copiedAttributes.append(attribute.copy() as! UICollectionViewLayoutAttributes)
    }

    for attributes in copiedAttributes {

        if let kind = attributes.representedElementKind {
            // Non nil : Supplementary View

            if kind == UICollectionElementKindSectionHeader {
                // [A] HEADER

                var frame = attributes.frame

                frame.origin.y = frame.origin.y + frame.size.height

                frame.size.width = sectionInset.left
                frame.size.height = 60 // Hard-coded - header height

                attributes.frame = frame        
            }
            else if kind == UICollectionElementKindSectionFooter {
                // [B] FOOTER

                var frame = attributes.frame

                // My footer view is a "hairline" taking most of the width
                // (save for 8 points of inset margin on each side):
                frame.origin.x += 8
                frame.size.width -= 16

                attributes.frame = frame
            }
        }
        else{
            // Kind is nil : Item (cell)

            if let attributesForItem = self.layoutAttributesForItemAtIndexPath(attributes.indexPath){

                attributes.frame = attributesForItem.frame
            }
        }     
    }

    return copiedAttributes
}

还有layoutAttributesForItemAtIndexPath()的实现,但是更多的是关于items-part的左对齐,所以我省略了它(如果有人有兴趣,请查看上面链接的github项目)。

一旦有时间,我将创建一个包含我的实施演示项目的存储库。

这些分别是集合视图和流布局对象的 Size Inspector 设置:

(顺便说一下,项目大小是可变的,由委托方法在运行时确定,所以忽略这些值)

我也采用了UICollectionViewDelegateFlowLayout协议,像这样:

func collectionView(collectionView: UICollectionView,
        layout collectionViewLayout: UICollectionViewLayout,
        referenceSizeForHeaderInSection section: Int) -> CGSize
{
    let left = (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset.left

    return CGSizeMake(left, 1)
}

虽然,老实说,一些布局属性可以在很多地方指定(接口生成器、布局对象的属性、委托方法的返回值等...),我不记得具体哪个优先在每种情况下,我都需要稍微清理一下我的代码。

【讨论】:

  • 太棒了。就我而言,我想要上面的部分标签,因为它是一个水平滚动集合。所以我使标签的宽度与该索引处的单元格相同。请看下面我的回答
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-02-18
  • 2012-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-30
相关资源
最近更新 更多