filletofish / CardsLayout

:star: Custom card-designed CollectionView layout
MIT License
856 stars 62 forks source link

Cards Swipe Direction #2

Open Gantaios opened 7 years ago

Gantaios commented 7 years ago

Hi thanks for your excellent repository i just want to have this thing how to change the swipe direction i want the cards to swipe up and down.

filletofish commented 7 years ago

Hi @Gantaios! Thank you for your notice.

In order to have direction of the layout vertical, not from right to left, one have to rewrite the class to use contentOffset.y.

Implementing this feature is on my roadmap. I will try to add it as soon as possible.

Viscmad commented 7 years ago

I have the same request. 👍

UPDATE : Looked into the code and did it myself.... https://pastebin.com/KjiYipEu Cheers!

filletofish commented 7 years ago

@Viscmad Hey! That's nice! Do you want to form it in separate Pull Request?

Viscmad commented 7 years ago

@filletofish Done 👍 :D

bimawa commented 5 years ago

guys, how can I do for direction left to right? :)

HappyIosDeveloper commented 3 years ago

I tested the @filletofish answer but the scroll has weird issues & it's not perfect. is there any other solution to change scroll direction to vertical?

HappyIosDeveloper commented 3 years ago

Finally I came up with this:

import UIKit

open class CardsCollectionViewLayout: UICollectionViewLayout {

// MARK: - Layout configuration
public var itemSize: CGSize = CGSize(width: 200, height: 100) {
    didSet{
        invalidateLayout()
    }
}

public var spacing: CGFloat = 10.0 {
    didSet{
        invalidateLayout()
    }
}

public var maximumVisibleItems: Int = 3 {
    didSet{
        invalidateLayout()
    }
}

// MARK: UICollectionViewLayout
override open var collectionView: UICollectionView {
    return super.collectionView!
}

override open var collectionViewContentSize: CGSize {
    let itemsCount = CGFloat(collectionView.numberOfItems(inSection: 0))
    return CGSize(width: collectionView.bounds.width * itemsCount, height: collectionView.bounds.height * itemsCount)
}

override open func prepare() {
    super.prepare()
    assert(collectionView.numberOfSections == 1, "Multiple sections aren't supported!")
}

override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    let totalItemsCount = collectionView.numberOfItems(inSection: 0)
    let minVisibleIndex = max(Int(collectionView.contentOffset.y) / Int(collectionView.bounds.height), 0)
    let maxVisibleIndex = min(minVisibleIndex + maximumVisibleItems, totalItemsCount)
    let contentCenterX = collectionView.contentOffset.x + (collectionView.bounds.width / 2.0)
    let deltaOffset = Int(collectionView.contentOffset.y) % Int(collectionView.bounds.height)
    let percentageDeltaOffset = CGFloat(deltaOffset) / collectionView.bounds.height
    let visibleIndices = minVisibleIndex..<maxVisibleIndex
    let attributes: [UICollectionViewLayoutAttributes] = visibleIndices.map { index in
        let indexPath = IndexPath(item: index, section: 0)
        return computeLayoutAttributesForItem(indexPath: indexPath, minVisibleIndex: minVisibleIndex, contentCenterX: contentCenterX, deltaOffset: CGFloat(deltaOffset), percentageDeltaOffset: percentageDeltaOffset)
    }
    return attributes
}

override open func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    let contentCenterX = collectionView.contentOffset.x + (collectionView.bounds.width / 2.0)
    let minVisibleIndex = Int(collectionView.contentOffset.x) / Int(collectionView.bounds.width)
    let deltaOffset = Int(collectionView.contentOffset.y) % Int(collectionView.bounds.height)
    let percentageDeltaOffset = CGFloat(deltaOffset) / collectionView.bounds.height
    return computeLayoutAttributesForItem(indexPath: indexPath, minVisibleIndex: minVisibleIndex, contentCenterX: contentCenterX, deltaOffset: CGFloat(deltaOffset), percentageDeltaOffset: percentageDeltaOffset)
}

override open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
    return true
}

}

// MARK: - Layout computations fileprivate extension CardsCollectionViewLayout {

private func scale(at index: Int) -> CGFloat {
    let translatedCoefficient = CGFloat(index) - CGFloat(self.maximumVisibleItems) / 2
    return CGFloat(pow(0.95, translatedCoefficient))
}

private func transform(atCurrentVisibleIndex visibleIndex: Int, percentageOffset: CGFloat) -> CGAffineTransform {
    var rawScale = visibleIndex < maximumVisibleItems ? scale(at: visibleIndex) : 1.0
    if visibleIndex != 0 {
        let previousScale = scale(at: visibleIndex - 1)
        let delta = (previousScale - rawScale) * percentageOffset
        rawScale += delta
    }
    return CGAffineTransform(scaleX: rawScale, y: rawScale)
}

func computeLayoutAttributesForItem(indexPath: IndexPath, minVisibleIndex: Int, contentCenterX: CGFloat, deltaOffset: CGFloat, percentageDeltaOffset: CGFloat) -> UICollectionViewLayoutAttributes {
    let attributes = UICollectionViewLayoutAttributes(forCellWith:indexPath)
    let visibleIndex = indexPath.row - minVisibleIndex
    //Using this to fill up screen with card with fixed % of margin
    attributes.size = CGSize(width: (collectionView.bounds.width - ((collectionView.bounds.width * 2)/100)), height: (collectionView.bounds.height - ((collectionView.bounds.height * 4)/100)))
    let midY = self.collectionView.bounds.midY
    attributes.center = CGPoint(x: contentCenterX , y: midY + spacing * CGFloat(visibleIndex)) // MARK: this x spacing value is the key to home page center cell xposition
    attributes.zIndex = maximumVisibleItems - visibleIndex
    attributes.transform = transform(atCurrentVisibleIndex: visibleIndex, percentageOffset: percentageDeltaOffset)
    switch visibleIndex {
    case 0:
        attributes.center.y -= deltaOffset
    case 1..<maximumVisibleItems:
        attributes.center.y -= spacing * percentageDeltaOffset
        if visibleIndex == maximumVisibleItems - 1 {
            attributes.alpha = percentageDeltaOffset
        }
    default:
        attributes.alpha = 0
    }
    return attributes
}

}