iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 14
0

https://ithelp.ithome.com.tw/upload/images/20180102/20107329GXAwJL9Ggz.png
在一些應用中我們會看到一些特殊的佈局方式比如 Pinterest 的瀑布流。

而我們只要通過自定義 UICollectionViewLayout 就可以實現。


CardLayout


可以設定畫面上最多會有幾張卡片,卡片向做滑動可以滑出畫面,向右可以將卡片滑入。

CardCell

https://ithelp.ithome.com.tw/upload/images/20180103/20107329K9pjhhJ9Dl.png
CardCell 只有放一張圖片,在 loadContent 後載入圖片、設定基本樣式。

class CardCell: UICollectionViewCell {

    @IBOutlet weak var imageView: UIImageView!
    var imageName:String?
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
    
    func loadContent() {
        if imageName == nil { return }
        if let image = UIImage(named:imageName!) {
            imageView.image = image
        }
        
        layer.cornerRadius = 20
        layer.borderColor = UIColor.white.cgColor
        layer.borderWidth = 2
    }

}

CardCollectionViewLayout

通過自定義 UICollectionViewLayout 來定義出卡片滑動的佈局效果。

基本參數

可以對 CardCollectionViewLayout 設定 itemSize / spacing / maximumVisibleItems

並且在設定的時候調用 invalidateLayout 方法,觸發畫面重新 render 機制,這裡會檢查 collectionView 是否存在。

public var itemSize: CGSize = CGSize(width: 250, height: 400) {
    didSet{
        if collectionView != nil {
            invalidateLayout()
        }
    }
}

public var spacing: CGFloat = 16.0 {
    didSet{
        if collectionView != nil {
            invalidateLayout()
        }
    }
}

public var maximumVisibleItems: Int = 4 {
    didSet{
        if collectionView != nil {
            invalidateLayout()
        }
    }
}

佈局計算

https://ithelp.ithome.com.tw/upload/images/20180103/201073297nr2hayzhV.png

attributes.center - 每張卡片在畫面上的位置,根據設定的 Spacing 讓卡片在 y 軸上有間距。

根據移動 UICollectionViewCell 的比例 (percentageDeltaOffset) 來改變即將登場卡片的 Alpha 值

// MARK: - compute layout
extension CardCollectionViewLayout {
    
    func computeLayoutAttributesForItem(indexPath: IndexPath,
                                        minVisibleIndex: Int,
                                        contentCenterX: CGFloat,
                                        deltaOffset: CGFloat,
                                        percentageDeltaOffset: CGFloat) -> UICollectionViewLayoutAttributes {
        if collectionView == nil { return UICollectionViewLayoutAttributes(forCellWith:indexPath)}
        
        let attributes = UICollectionViewLayoutAttributes(forCellWith:indexPath)
        let cardIndex = indexPath.row - minVisibleIndex
        attributes.size = itemSize
        attributes.center = CGPoint(x: contentCenterX + spacing * CGFloat(cardIndex),
                                    y: collectionView!.bounds.midY + spacing * CGFloat(cardIndex))
        attributes.zIndex = maximumVisibleItems - cardIndex
        
        switch cardIndex {
        case 0:
            attributes.center.x -= deltaOffset
            
        case 1..<maximumVisibleItems:
            attributes.center.x -= spacing * percentageDeltaOffset
            attributes.center.y -= spacing * percentageDeltaOffset
            
            if cardIndex == maximumVisibleItems - 1 {
                attributes.alpha = percentageDeltaOffset
            }
            
        default: break
            
        }
        return attributes
    }
}

返回佈局屬性

這次的佈局只有支持單一 section 所以在 prepare 的地方會先檢查 section 的數量。

  • collectionViewContentSize 需要回傳 UICollectionView 內容的大小(不只是可視範圍)類似於UIScrollView 的 contentSize
  • layoutAttributesForElements 可視 (in rect) 範圍內所有單元格的屬性
// MARK: UICollectionViewLayout
extension CardCollectionViewLayout {
    override open func prepare() {
        super.prepare()
        assert(collectionView?.numberOfSections == 1, "Multiple sections aren't supported!")
    }
    
    override open var collectionViewContentSize: CGSize {
        if collectionView == nil { return CGSize.zero }
        
        let itemsCount = CGFloat(collectionView!.numberOfItems(inSection: 0))
        return CGSize(width: collectionView!.bounds.width * itemsCount,
                      height: collectionView!.bounds.height)
    }
    
    override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        if collectionView == nil { return nil }
        
        let totalItemsCount = collectionView!.numberOfItems(inSection: 0)
        let minVisibleIndex = max(0, Int(collectionView!.contentOffset.x) / Int(collectionView!.bounds.width))
        let maxVisibleIndex = min(totalItemsCount, minVisibleIndex + maximumVisibleItems)
        
        let contentCenterX = collectionView!.contentOffset.x + collectionView!.bounds.width / 2
        let deltaOffset = Int(collectionView!.contentOffset.x) % Int(collectionView!.bounds.width)
        let percentageDeltaOffset = CGFloat(deltaOffset) / collectionView!.bounds.width
        
        var attributes = [UICollectionViewLayoutAttributes]()
        for i in minVisibleIndex..<maxVisibleIndex {
            let attribute = computeLayoutAttributesForItem(indexPath: IndexPath(item: i, section: 0),
                                                           minVisibleIndex: minVisibleIndex,
                                                           contentCenterX: contentCenterX,
                                                           deltaOffset: CGFloat(deltaOffset),
                                                           percentageDeltaOffset: percentageDeltaOffset)
            attributes.append(attribute)
        }

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

筆記

優化

layoutAttributesForElements 的計算是根據 in rect 範圍推算目前在畫面上的 index 然後計算對應的 attributes 後返回。

可以進一步的將計算過的 attribute 存起來,在這個方法中直接返回,這樣可以避免一直重複計算。

疑問 1

layoutAttributesForElements 以及 layout AttributesForItem 的關係?

在拖動 UICollectionViewCell 的過程中

疑問 2

collectionView.bounds 的大小是隨著往左滑動的卡片數量增長的嗎?


Reference


上一篇
LineageM Collection - 篩選卡片
下一篇
Flat Card Layout
系列文
iOS Swift x Layout x Animation x Transition30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
吳晉榮
iT邦新手 5 級 ‧ 2018-01-02 11:47:32

不要富奸

不會的 lol~~~~

0
陳董粉絲
iT邦新手 5 級 ‧ 2018-01-10 11:02:14

疑問 2
collectionView.bounds 的大小是隨著往左滑動的卡片數量增長的嗎?
==>collectionView.bounds不是固定的嗎?

我要留言

立即登入留言