Iglistkit: How do you implement separators?

Created on 14 Dec 2016  Â·  16Comments  Â·  Source: Instagram/IGListKit

New issue checklist

General information

Usually, inside a section there are different blocks and these blocks use a separator, usually a one point grey line.
What is the approach you use to implement separators when using this library?
Do you use a special view, a margin or separators like UIView (minimumLineSpacing)? Or maybe a custom layout (because it is a UICollectionView).
Thanks for suggestions.

question

Most helpful comment

I'd highly recommend using supplementary views.

  • This is way more flexible and easier to change in the future
  • Separators really aren't part of the cell
  • You don't have to mess with your cell layout at all when you change how the separators work/look
  • Fewer edge cases (do you keep the separator on the last cell or not?)
  • Separators don't receive touch events
  • The other solutions are really just hacks

😄

All 16 comments

I've seen a couple succesful implementations:

  • Create a thin supplementary view as the separator
  • Add a thin CALayer to your cells and lay it out on the top/bottom
  • Use thin UICollectionViewCells

All solutions work pretty well, and it depends on the complexity of the separators (between sections, between items in cells, first/last items have separators, etc).

I'd highly recommend using supplementary views.

  • This is way more flexible and easier to change in the future
  • Separators really aren't part of the cell
  • You don't have to mess with your cell layout at all when you change how the separators work/look
  • Fewer edge cases (do you keep the separator on the last cell or not?)
  • Separators don't receive touch events
  • The other solutions are really just hacks

😄

I'm having a hard time picturing how supplementary views would work for separators if the IGListSectionController has multiple items in it. For example, how would you use that approach for the FeedItemController in the SupplementaryViewController example? Supplementary views can be used for headers and footers for a section, but not between items in a section.

@jeffbailey if you use a custom UICollectionViewLayout subclass that'd be doable, but you're right with multiple items and UICollectionViewFlowLayout, supplementary views wouldn't work.

Using inset = UIEdgeInsets(top: 7, left: 0, bottom: 7, right: 0) I get a separation between 2 sections.

And using minimumLineSpacing = 1 adds a separation between the cells that are inside a section.

It seems with that it is not necessary to use Supplementary views, at least if you want simple separators, like my case.

Thanks a lot for suggestions!

My need is to have a section with multiple cells, some separated by a thin gray line, some not.

Following this discussion, I tried to implement separators using the UICollectionViewFlowLayout decoration views (seemed to be it's more appropriate than supplementary views). So as suggested by @rnystrom, I subclassed UICollectionViewFlowLayout to implement my own decoration view (see my code below)

However, I cannot find a way to access the section (IGListSectionController) from layoutAttributesForElements(in rect: CGRect). This section would indicate me the cells needing a separator and the one not needing it.

At the moment, the implementation below adds a decoration view to every cell. Would I have more luck with supplementary views? Or will I have the same problem?

private let decoratorIdentifier: String = "horizontalDecorator"

final class FeedCollectionViewFlowLayout: UICollectionViewFlowLayout {

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

        var mutableAttributes = attributes
        for attribute in attributes {
            if let horizontal = self.layoutAttributesForDecorationView(ofKind: decoratorIdentifier,
                                                                       at: attribute.indexPath) {
                mutableAttributes.append(horizontal)
            }
        }

        return mutableAttributes
    }

    override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath)
        -> UICollectionViewLayoutAttributes? {
        guard let itemAttributes = self.layoutAttributesForItem(at: indexPath) else { return nil }

        let attributes = FeedCollectionViewLayoutAttributes(forDecorationViewOfKind: elementKind,
                                                            with: indexPath)
        attributes.zIndex = itemAttributes.zIndex + 1
        attributes.backgroundColor = UIColor.red

        var decorationViewFrame = itemAttributes.frame
        if elementKind == decoratorIdentifier {
            decorationViewFrame.size.height = 2.0
        }

        attributes.frame = decorationViewFrame

        return attributes
    }
}

final class FeedCollectionViewLayoutAttributes: UICollectionViewLayoutAttributes {
    var backgroundColor: UIColor?
}

final class FeedSeparatorView: UICollectionReusableView {

    override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
        super.apply(layoutAttributes)

        if let attributes = layoutAttributes as? FeedCollectionViewLayoutAttributes {
            self.backgroundColor = attributes.backgroundColor
        }
    }
}

And in my view controller:

let collectionView: IGListCollectionView = {

        let feedLayout = FeedCollectionViewFlowLayout()
        feedLayout.estimatedItemSize = CGSize(width: 320.0, height: 350.0)

        let collectionView = IGListCollectionView(frame: .zero, collectionViewLayout: feedLayout)

         return collectionView
}()

override func viewDidLoad() {
        //...

        self.collectionView.collectionViewLayout.register(FeedSeparatorView.self, forDecorationViewOfKind: decoratorIdentifier)

        //...
}

I wonder the same.

@Nonepse you'll need to create a custom delegate/dataSource for your layout that requests decoration information, similar to how UICollectionFlowLayout uses the UICollectionViewDelegateFlowLayout to get cell and supplementary size information.

Try something like this:

protocol MyCustomLayoutDecorationDelegate {
  func collectionView(
    _ collectionView: UICollectionView, 
    layout: MyCustomLayout, 
    indexPath: IndexPath
  ) -> CGSize
}

class MyCustomLayout: UICollectionViewFlowLayout {
  weak var decorationDelegate: MyCustomLayoutDecorationDelegate?

  // query the decorationDelegate for a size in layoutAttributesForDecorationView(...)
  // figure out the origin
}

class MyViewController: UIViewController, MyCustomLayoutDecorationDelegate {
  var adapter: IGListAdapter
  var layout: MyCustomLayout
  // etc

  func viewDidLoad() {
    super.viewDidLoad()
    layout.decorationSource = self
  }

  func collectionView(
    _ collectionView: UICollectionView, 
    layout: MyCustomLayout, 
    indexPath: IndexPath
  ) -> CGSize {
    // use adapter.sectionController(section: indexPath.section)
    // return a decoration size if necessary
  }
}

If that doesn't work, lets open up a new issue to track using decoration views with IGListKit.

Supplementary views are probably going to be easier since UICollectionViewFlowLayout supports them, and IGListKit has an API built for them as well.

@ryanolsonk Your solution works indeed, thanks! Do you think it would be worth it that I make a PR to add this as an example?

However, I am still facing an issue with the decoration views. When opening the collection view, they are not correctly located. When scrolling, they move to the proper position:

6025_separator_layout_issue

I think that has something to do with the fact I am using self-sizing cells. At opening, the decoration views are displayed where they expect the cell to be, but then the cell is re-sized. When scrolling it obviously triggers a layout event which fixes that. My best guess would to manually trigger this event (layout invalidation?) when cells are displayed, but I don't exactly know when I should trigger what :)

I think that has something to do with the fact I am using self-sizing cells

Just to clarify, if I don't use an estimatedItemSize on the collection view layout object, the decorations views are correctly positioned.
But obviously my cells are not the size I want.

Can you create an isolate test sample in a new project to check that?
If you have time or run out of ideas.

Enviado desde mi iPhone.

El 21 mar 2017, a las 13:10, Pierre Espenan notifications@github.com escribió:

I think that has something to do with the fact I am using self-sizing cells
Just to clarify, if I don't use an estimatedItemSize on the collection view layout object, the decorations views are correctly positioned.
But obviously my cells are not the size I want.

—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub, or mute the thread.

I meant to mention @rnystrom, not the other Ryan 😬

@Nonepse no worries 😄 all Ryans welcome

+1 to @Ricardo1980 about an example project. We're probably drifting into territory that IGListKit doesn't support since this is very much layout related. Again, a new issue would be great to track discussion on decoration views separately!

@Nonepse Hey, your feed View Controller looks really good! I was wondering if you can give me some tips to make a feed like yours. Like what backend server did you use and how did you use IGLIstKit to make the View Controller like yours. I would really appreciate it if you can give me some advice to make my feed look like that. Thank you so much!

I came across a similar issue. For reference, with self-sizing cells, you need to invalidate your decoration elements when invalidateLayout is called in UICollectionViewFlowLayout:

override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
    guard let indexPaths = context.invalidatedItemIndexPaths else {
        return
    }
    context.invalidateDecorationElements(ofKind: "your_separator_kind", at: indexPaths)
    super.invalidateLayout(with: context)
}
Was this page helpful?
0 / 5 - 0 ratings