LoginSignup
36
34

More than 5 years have passed since last update.

【Swift】UICollectionViewで無限スクロールをやってみた

Last updated at Posted at 2015-05-19

こんな感じです。

目が痛くなるような感じでごめんなさい。

SampleInfinityCollectionView.gif

上記のような感じのやつを試してみました。

今回もソースコードの紹介をしたいと思います。

とりあえずコピペで動くようにはなっているはずです。

ソースコードの置き場

いつも通り GitHubにおいてきました。

ryokosuge/SampleInfinityCollectionView

ソースコードの紹介

CollectionViewCell.swift
import UIKit

class CollectionViewCell: UICollectionViewCell {

    @IBOutlet weak var label: UILabel!

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func awakeFromNib() {
        super.awakeFromNib()
    }

}
CollectionView.swift
import UIKit

class CollectionView: UICollectionView {

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        let currentOffset = contentOffset
        let contentWidth = contentSize.width
        let contentHeight = contentSize.height

        let centerOffsetX = (contentWidth - bounds.width) / 2.0
        let centerOffsetY = (contentHeight - bounds.height) / 2.0

        let distanceFromCenterX = fabsf(Float(currentOffset.x - centerOffsetX))
        let distanceFromCenterY = fabsf(Float(currentOffset.y - centerOffsetY))

        if distanceFromCenterX > Float(contentWidth / 5.0) {
            contentOffset = CGPoint(x: centerOffsetX, y: currentOffset.y)
        }

        if distanceFromCenterY > Float(contentHeight / 5.0) {
            contentOffset = CGPoint(x: currentOffset.x, y: centerOffsetY)
        }
    }

}
MultipleLineLayout.swift
import UIKit

class MultipleLineLayout: UICollectionViewFlowLayout {

    var itemHeight: Int
    var itemWidth: Int
    var space: Int

    init(itemHeight:Int, itemWidth: Int, space: Int) {

        self.itemHeight = itemHeight
        self.itemWidth = itemWidth
        self.space = space

        super.init()
        scrollDirection = .Horizontal
    }

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func collectionViewContentSize() -> CGSize {
        if let collectionView = collectionView {
            let xSize = collectionView.numberOfItemsInSection(0) * (itemWidth + space)
            let ySize = collectionView.numberOfSections() * (itemHeight + space)
            return CGSize(width: xSize, height: ySize)
        }
        return CGSizeZero
    }

    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
        let attributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
        attributes.size = CGSize(width: itemWidth, height: itemHeight)
        let xValue = itemWidth / 2 + indexPath.row * (itemWidth + space)
        let yValue = itemHeight + indexPath.section * (itemHeight + space)
        attributes.center = CGPoint(x: xValue, y: yValue)
        return attributes
    }

    override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
        var attributes: [UICollectionViewLayoutAttributes] = []
        let minRow: Int = (rect.origin.x > 0.0) ? (Int(rect.origin.x) / Int(itemWidth + space)) : 0
        let maxRow: Int = Int(rect.size.width) / (itemWidth + space) + minRow
        let sectionNum: Int = collectionView?.numberOfSections() ?? 0
        for i in 0...(sectionNum - 1) {
            for j in minRow...maxRow {
                let indexPath = NSIndexPath(forItem: j, inSection: i)
                let attribute = layoutAttributesForItemAtIndexPath(indexPath)
                attributes.append(attribute)
            }
        }

        return attributes
    }

}
ViewController.swift
import UIKit

class ViewController: UIViewController {

    private let cellData: [[String]] = [
        ["1", "2", "3", "4", "5", "6"],
        ["7", "8", "9", "10", "11", "12"],
        ["13", "14", "15", "16", "17", "18"],
        ["19", "20", "21", "22", "23", "24"]
    ]

    var itemHeight: Int = 60
    var itemWidth: Int = 60
    var space: Int = 10

    @IBOutlet weak var collectionView: CollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        setupCollectionView()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

// MARK : - setup

extension ViewController {

    private func setupCollectionView() {
        let multpleLayout = MultipleLineLayout(itemHeight: itemHeight, itemWidth: itemWidth, space: space)
        collectionView.collectionViewLayout = multpleLayout
        collectionView.showsHorizontalScrollIndicator = false
        collectionView.showsVerticalScrollIndicator = false
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.reloadData()
    }

}

// MARK : - delegate

extension ViewController: UICollectionViewDelegate {

    func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
        println("indexPath.section = \(indexPath.section % 4) / indexPath.row = \(indexPath.row % 6)")
    }

}

extension ViewController: UICollectionViewDataSource {

    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 30
    }

    func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        return 20
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CollectionViewCell
        cell.label.text = cellData[indexPath.section % 4][indexPath.row % 6]
        return cell
    }

}

簡単な紹介

やっていることを簡単に紹介します。

UICollectionCollectionViewFlowLayoutで大きめのContentSizeを返す

まずcollectionView.contentSizeを大きくするために自分の要素(ViewControllerで言えばcellData: [[String]])を5倍の大きさにして表示しています。


# 表示するData
private let cellData: [[String]] = [
    ["1", "2", "3", "4", "5", "6"],
    ["7", "8", "9", "10", "11", "12"],
    ["13", "14", "15", "16", "17", "18"],
    ["19", "20", "21", "22", "23", "24"]
]

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    # 要素数は6なので5倍して30を返す
    return 30
}

func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
    # セクション数は4なので5倍して20返す
    return 20
}

で、それを利用してMultipleLineLayout.swift

override func collectionViewContentSize() -> CGSize {
    if let collectionView = collectionView {
        let xSize = collectionView.numberOfItemsInSection(0) * (itemWidth + space)
        let ySize = collectionView.numberOfSections() * (itemHeight + space)
        return CGSize(width: xSize, height: ySize)
    }
    return CGSizeZero
}

で大きめのContentSizeを返しています。

UICollectionViewのlayoutSubviewsを利用して無限スクロール感を出す

そしてUICollectionViewを継承したクラスでlayoutSubviews()overrideして位置を調整しています。

class CollectionView: UICollectionView {
    ~ 省略 ~
    override func layoutSubviews() {
        super.layoutSubviews()

        let currentOffset = contentOffset
        let contentWidth = contentSize.width
        let contentHeight = contentSize.height

        let centerOffsetX = (contentWidth - bounds.width) / 2.0
        let centerOffsetY = (contentHeight - bounds.height) / 2.0

        let distanceFromCenterX = fabsf(Float(currentOffset.x - centerOffsetX))
        let distanceFromCenterY = fabsf(Float(currentOffset.y - centerOffsetY))

        if distanceFromCenterX > Float(contentWidth / 5.0) {
            contentOffset = CGPoint(x: centerOffsetX, y: currentOffset.y)
        }

        if distanceFromCenterY > Float(contentHeight / 5.0) {
            contentOffset = CGPoint(x: currentOffset.x, y: centerOffsetY)
        }
    }
}

という流れになっています。

終わりに

とりあえず、かっこいいの作れたなと思いましたが、改めて見たら目が痛くなるだけでした...

これは高さと幅を固定にしていますが、高さ = UICollectionViewの高さになるように作れれば横のみ無限スクロールなどもできました。

以上です。

36
34
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
34