iOS中的2向滚动表

时间:2018-08-21 09:12:16

标签: ios swift uitableview horizontal-scrolling

我是一名Android应用程序开发人员,是iOS编程的新手,我的第一个挑战是在iOS中构建2向滚动表。我在UITableView内使用UICollectionView获得了许多解决方案。但是在我的情况下,行将滚动在一起,而不是彼此独立。表格中有超过15列和100多个带有文本数据的行。

我通过在Horizo​​ntalScrollView内使用ListView在Android中实现了同样的效果。但尚未在iOS中找到任何解决方案。任何帮助将不胜感激。

编辑:我添加了android应用程序的几个屏幕,其中表格在水平方向滚动。

This is starting position

This is after scrolling horizontally

1 个答案:

答案 0 :(得分:2)

所以你想要这个:

demo

您应该使用UICollectionView。您不能使用UICollectionViewFlowLayout(公共SDK中唯一提供的布局),因为它只能在一个方向上滚动,因此您需要实现一个自定义的UICollectionViewLayout子类,该子类将元素布置为如果需要,可以双向滚动。

有关构建自定义UICollectionViewLayout子类的完整详细信息,您应该观看以下内容:WWDC 2012中的视频:

无论如何,我将在此处转储GridLayout的示例实现,以供您开始使用。对于每个IndexPath,我将section作为行号,将item作为列号。

class GridLayout: UICollectionViewLayout {
    var cellHeight: CGFloat = 22
    var cellWidths: [CGFloat] = [] {
        didSet {
            precondition(cellWidths.filter({ $0 <= 0 }).isEmpty)
            invalidateCache()
        }
    }

    override var collectionViewContentSize: CGSize {
        return CGSize(width: totalWidth, height: totalHeight)
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        // When bouncing, rect's origin can have a negative x or y, which is bad.
        let newRect = rect.intersection(CGRect(x: 0, y: 0, width: totalWidth, height: totalHeight))

        var poses = [UICollectionViewLayoutAttributes]()
        let rows = rowsOverlapping(newRect)
        let columns = columnsOverlapping(newRect)
        for row in rows {
            for column in columns {
                let indexPath = IndexPath(item: column, section: row)
                poses.append(pose(forCellAt: indexPath))
            }
        }

        return poses
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return pose(forCellAt: indexPath)
    }

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

    private struct CellSpan {
        var minX: CGFloat
        var maxX: CGFloat
    }

    private struct Cache {
        var cellSpans: [CellSpan]
        var totalWidth: CGFloat
    }

    private var _cache: Cache? = nil
    private var cache: Cache {
        if let cache = _cache { return cache }
        var spans = [CellSpan]()
        var x: CGFloat = 0
        for width in cellWidths {
            spans.append(CellSpan(minX: x, maxX: x + width))
            x += width
        }
        let cache = Cache(cellSpans: spans, totalWidth: x)
        _cache = cache
        return cache
    }

    private var totalWidth: CGFloat { return cache.totalWidth }
    private var cellSpans: [CellSpan] { return cache.cellSpans }

    private var totalHeight: CGFloat {
        return cellHeight * CGFloat(collectionView?.numberOfSections ?? 0)
    }

    private func invalidateCache() {
        _cache = nil
        invalidateLayout()
    }

    private func rowsOverlapping(_ rect: CGRect) -> Range<Int> {
        let startRow = Int(floor(rect.minY / cellHeight))
        let endRow = Int(ceil(rect.maxY / cellHeight))
        return startRow ..< endRow
    }

    private func columnsOverlapping(_ rect: CGRect) -> Range<Int> {
        let minX = rect.minX
        let maxX = rect.maxX
        if let start = cellSpans.firstIndex(where: { $0.maxX >= minX }), let end = cellSpans.lastIndex(where: { $0.minX <= maxX }) {
            return start ..< end + 1
        } else {
            return 0 ..< 0
        }
    }

    private func pose(forCellAt indexPath: IndexPath) -> UICollectionViewLayoutAttributes {
        let pose = UICollectionViewLayoutAttributes(forCellWith: indexPath)
        let row = indexPath.section
        let column = indexPath.item
        pose.frame = CGRect(x: cellSpans[column].minX, y: CGFloat(row) * cellHeight, width: cellWidths[column], height: cellHeight)
        return pose
    }
}

要绘制分隔线,我在每个单元格的背景中添加了细线视图:

class GridCell: UICollectionViewCell {
    static var reuseIdentifier: String { return "cell" }

    override init(frame: CGRect) {
        super.init(frame: frame)
        label.frame = bounds.insetBy(dx: 2, dy: 2)
        label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        contentView.addSubview(label)

        let backgroundView = UIView(frame: CGRect(origin: .zero, size: frame.size))
        backgroundView.backgroundColor = .white
        self.backgroundView = backgroundView

        rightSeparator.backgroundColor = .gray
        backgroundView.addSubview(rightSeparator)

        bottomSeparator.backgroundColor = .gray
        backgroundView.addSubview(bottomSeparator)
    }

    func setRecord(_ record: String) {
        label.text = record
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        let thickness = 1 / (window?.screen.scale ?? 1)
        let size = bounds.size
        rightSeparator.frame = CGRect(x: size.width - thickness, y: 0, width: thickness, height: size.height)
        bottomSeparator.frame = CGRect(x: 0, y: size.height - thickness, width: size.width, height: thickness)
    }

    private let label = UILabel()
    private let rightSeparator = UIView()
    private let bottomSeparator = UIView()

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

这是我的演示视图控制器:

class ViewController: UIViewController {

    var records: [[String]] = (0 ..< 20).map { row in
        (0 ..< 6).map {
            column in
            "Row \(row) column \(column)"
        }
    }

    var cellWidths: [CGFloat] = [ 180, 200, 180, 160, 200, 200 ]

    override func viewDidLoad() {
        super.viewDidLoad()

        let layout = GridLayout()
        layout.cellHeight = 44
        layout.cellWidths = cellWidths
        let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
        collectionView.isDirectionalLockEnabled = true
        collectionView.backgroundColor = UIColor(white: 0.95, alpha: 1)
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        collectionView.register(GridCell.self, forCellWithReuseIdentifier: GridCell.reuseIdentifier)
        collectionView.dataSource = self
        view.addSubview(collectionView)
    }

}

extension ViewController: UICollectionViewDataSource {
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return records.count
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return records[section].count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: GridCell.reuseIdentifier, for: indexPath) as! GridCell
        cell.setRecord(records[indexPath.section][indexPath.item])
        return cell
    }
}