Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple Section Header Overlapping Cells #13

Open
abkama0a opened this issue Aug 5, 2018 · 2 comments
Open

Multiple Section Header Overlapping Cells #13

abkama0a opened this issue Aug 5, 2018 · 2 comments

Comments

@abkama0a
Copy link

abkama0a commented Aug 5, 2018

I am having a hard time trying to add header sections along with your awesome AlignedcollectionViewFlowLayout, but my attempts are failing.

What I did so far was adding layoutAttributesForSupplementaryView:ofKind:at to your .swift file and modifying your setFrame function to process UICollectionElementKindSectionHeader. I also added a public var for header's height: headerHeight

override open func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        
        guard let attributes = super.layoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
            print("nill called attributes")
            return nil
        }
        print("first cell frame = \(layoutAttributesForItem(at: indexPath)!.frame)")
        let yPos:CGFloat = layoutAttributesForItem(at: indexPath)!.frame.origin.y - headerHeight
        attributes.frame = CGRect(x: 0.0, y: yPos, width: (collectionView?.frame.width)!, height: headerHeight)
        
        return attributes
    }

and

/// Sets the frame for the passed layout attributes object by calling the `layoutAttributesForItem(at:)` function.
    private func setFrame(forLayoutAttributes layoutAttributes: UICollectionViewLayoutAttributes) {
        if layoutAttributes.representedElementCategory == .cell { // Do not modify header views etc.
            let indexPath = layoutAttributes.indexPath
            if let newFrame = layoutAttributesForItem(at: indexPath)?.frame {
                layoutAttributes.frame = newFrame
            }
        } else if layoutAttributes.representedElementCategory == .supplementaryView {
            if layoutAttributes.representedElementKind == UICollectionElementKindSectionHeader {
                if let newFrame = layoutAttributesForSupplementaryView(ofKind: UICollectionElementKindSectionHeader, at: layoutAttributes.indexPath)?.frame {
                    layoutAttributes.frame = newFrame
                }
            }
        }
    }

My implementation seems to work right, but when I segue to my ViewController where I am implementing your custom flow layout, viewForSupplementaryElementOfKind is called too soon before the correct attributes are calculated. I know this because I am triggering a call to viewForSupplementaryElementOfKind by reloadItems(at:) inside collectionView:didSelectItemAt

(Edit: I added the following block to my viewDidAppear to work around this delay

UIView.performWithoutAnimation {
            filterCollection.reloadItems(at: [IndexPath(row: patterns.count - 1, section: 2)])
        }

)

I've also printed frame values for first cell in section as well as the header frame. It seems that my attributes are calculated 4 times and the viewForSupplementaryElementOfKind function takes values from the 3rd call

Edit:
see attachment for screens after segueing to VC, log from console about first cell in section and header saved frame, and screen of collectionView after triggering didSelectItemAt or reloadingItems in viewDidAppear

I'd really appreciate it if you'd help me to avoid making extra call to reloadItems(at:)

log after segue to vc
after segue to vc
after click at item at index

@adriantabirta
Copy link

I have the same problem, how to solve it?

@abkama0a
Copy link
Author

Here's my current workaround which I think is not a perfect solution:

add the following extension and custom class to your project:

class CVHeader {
    var value: CGFloat = 30.0
}

private var headerKey: UInt8 = 0
extension AlignedCollectionViewFlowLayout {
    var headerHeight: CVHeader {
        get {
            return associatedObject(base: self, key: &headerKey)
            { return CVHeader() }
        }
        set { associateObject(base: self, key: &headerKey, value: newValue) }
    }
    
    func associatedObject<ValueType: AnyObject>(
        base: AnyObject,
        key: UnsafePointer<UInt8>,
        initialiser: () -> ValueType)
        -> ValueType {
            if let associated = objc_getAssociatedObject(base, key)
                as? ValueType { return associated }
            let associated = initialiser()
            objc_setAssociatedObject(base, key, associated,
                                     .OBJC_ASSOCIATION_RETAIN)
            return associated
    }
    func associateObject<ValueType: AnyObject>(
        base: AnyObject,
        key: UnsafePointer<UInt8>,
        value: ValueType) {
        objc_setAssociatedObject(base, key, value,
                                 .OBJC_ASSOCIATION_RETAIN)
    }
    
    override open func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let attributes = super.layoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
            return nil
        }
        
        let yPos:CGFloat = layoutAttributesForItem(at: indexPath)!.frame.origin.y - headerHeight.value
        attributes.frame = CGRect(x: 0.0, y: yPos, width: (collectionView?.frame.width)!, height: headerHeight.value)
        
        return attributes
    }
}

Then, in your viewController where you are implementing the AlignedCollectionViewFlowLayout, override the viewDidAppear function as follows:

override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        UIView.performWithoutAnimation {
            yourCollectionView.reloadItems(at: [IndexPath(row: yourArray.count - 1, section: yourLastSectionIndex)])
        }
    }

You'll also need to edit the AlignedCollectionViewFlowLayout.swift file. You need to edit the private setFrame function as follows:

private func setFrame(forLayoutAttributes layoutAttributes: UICollectionViewLayoutAttributes) {
        if layoutAttributes.representedElementCategory == .cell { // Do not modify header views etc.
            let indexPath = layoutAttributes.indexPath
            if let newFrame = layoutAttributesForItem(at: indexPath)?.frame {
                layoutAttributes.frame = newFrame
            }
        } else if layoutAttributes.representedElementCategory == .supplementaryView {
            if layoutAttributes.representedElementKind == UICollectionElementKindSectionHeader {
                if let newFrame = layoutAttributesForSupplementaryView(ofKind: UICollectionElementKindSectionHeader, at: layoutAttributes.indexPath)?.frame {
                    layoutAttributes.frame = newFrame
                }
            }
        }
    }

if setFrame wasn't private, we could've overridden it in the extension and never had to worry about updating this in the future if the original author update this library. I hope this help you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants