Swift多列表格组件的基本功能的例子

与桌面、Web应用不同,受限于屏幕尺寸,移动APP常常采用单列表格来显示列表数据。但有时我们需要使用多列表格来展示数据(比如:报表数据显示,或iPad这种大屏设备上展示多栏数据),这些通过网格(UICollectionView)的自定义布局功能就可以实现。

 

1,多列表格(multi-column table control)效果图

2,功能说明:

(1)表格列头的标题文字加粗,内容区域的文字正常

(2)表格边框为1像素黑色边框

(3)第一列文字居左,其余列文字居中显示(居左的文字离左侧还是有5个像素距离)

(4)每列单元格宽度不是平均分配的。而是从右往左,根据表头文字计算当前列的宽度。剩下的空间就都分配给第一列。

(5)整个组件内部设置了 contentInset,给左右两侧各设置了10像素的距离。这样组件外部设置100%宽时,左右边框也不会顶到屏幕边缘。同时如果有滚动条的时候,滚动条也不会盖在表格内容区域上方。

(6)点击单元格控制台会打印出对应的坐标位置。

 

3,关于collection view重新计算布局时机
(1)shouldInvalidateLayoutForBoundsChange() 方法返回 true,表示当 collection view 的 bounds 改变时,就要重新计算布局。
(2)除了collection view 改变尺寸大小时 bounds 会改变, scroll view 的 bounds 在滚动时也会改变。
(3)本例中,collection view 在滚动的情况下没必要计算更新布局,否则拖动滚动条的时候布局会不断地丢弃重新计算,影响性能。 

(4)这里在 shouldInvalidateLayoutForBoundsChange() 中做判断,只有 collection view 宽度变化时才返回true重新计算布局,否则返回false。

 

4,项目代码

--- UICollectionGridViewController.swift(组件类) ---

import Foundation
import UIKit
 
//多列表格组件(通过CollectionView实现)
class UICollectionGridViewController: UICollectionViewController {
    //表头数据
    var cols: [String]! = []
    //行数据
    var rows: [[AnyObject]]! = []
    //单元格内容居左时的左侧内边距
    private var cellPaddingLeft:CGFloat = 5
     
    init() {
        //初始化表格布局
        let layout = UICollectionGridViewLayout()
        super.init(collectionViewLayout: layout)
        layout.viewController = self      
        collectionView!.backgroundColor = UIColor.whiteColor()
        collectionView!.registerClass(UICollectionViewCell.self,
            forCellWithReuseIdentifier: "cell")
        collectionView!.delegate = self
        collectionView!.dataSource = self
        collectionView!.directionalLockEnabled = true
        collectionView!.contentInset = UIEdgeInsetsMake(0, 10, 0, 10)
        collectionView!.bounces = false
    }
     
    required init?(coder aDecoder: NSCoder) {
        fatalError("UICollectionGridViewController.init(coder:) has not been implemented")
    }
     
    //设置列头数据
    func setColumns(columns: [String]) {
        cols = columns
    }
     
    //添加行数据
    func addRow(row: [AnyObject]) {
        rows.append(row)
        collectionView!.collectionViewLayout.invalidateLayout()
        collectionView!.reloadData()
    }
     
    override func viewDidLoad() {
        super.viewDidLoad()
    }
     
    override func viewDidLayoutSubviews() {
        collectionView!.frame = CGRectMake(0, 0, view.frame.width, view.frame.height)
    }
     
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
     
    //返回表格总行数
    override func numberOfSectionsInCollectionView(collectionView: UICollectionView)
        -> Int {
        if cols.isEmpty {
            return 0
        }
        //总行数是:记录数+1个表头
        return rows.count + 1
    }
     
    //返回表格的列数
    override func collectionView(collectionView: UICollectionView,
        numberOfItemsInSection section: Int) -> Int {
            return cols.count
    }
     
    //单元格内容创建
    override func collectionView(collectionView: UICollectionView,
        cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
             
            let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell",
                forIndexPath: indexPath) as UICollectionViewCell           
            //单元格边框
            cell.layer.borderWidth = 1
            cell.backgroundColor = UIColor.whiteColor()
            cell.clipsToBounds = true
             
            //先清空内部原有的元素
            for subview in cell.subviews {
                subview.removeFromSuperview()
            }
             
            //添加内容标签
            let label = UILabel(frame: CGRectMake(0, 0, cell.frame.width,
                cell.frame.height))
             
            //第一列的内容左对齐,其它列内容居中
            if indexPath.row != 0 {
                label.textAlignment = NSTextAlignment.Center
            }else {
                label.textAlignment = NSTextAlignment.Left
                label.frame.origin.x = cellPaddingLeft
            }
             
            //设置列头单元格,内容单元格的数据
            if indexPath.section == 0 {
                let text = NSAttributedString(string: cols[indexPath.row], attributes: [
                    NSFontAttributeName:UIFont.boldSystemFontOfSize(15)
                    ])
                label.attributedText = text
            } else {
                label.font = UIFont.systemFontOfSize(15)
                label.text = "\(rows[indexPath.section-1][indexPath.row])"
            }
            cell.addSubview(label)
             
            return cell
    }
     
    //单元格选中事件
    override func collectionView(collectionView: UICollectionView,
        didSelectItemAtIndexPath indexPath: NSIndexPath) {
            //打印出点击单元格的[行,列]坐标
            print("点击单元格的[行,列]坐标: [\(indexPath.section),\(indexPath.row)]")
    }
}

--- UICollectionGridViewLayout.swift(布局类) ---
import Foundation
import UIKit
 
//多列表格组件布局类
class UICollectionGridViewLayout: UICollectionViewLayout {
    //记录每个单元格的布局属性
    private var itemAttributes: [[UICollectionViewLayoutAttributes]] = []
    private var itemsSize: [NSValue] = []
    private var contentSize: CGSize = CGSizeZero
    //表格组件视图控制器
    var viewController: UICollectionGridViewController!
     
    //准备所有view的layoutAttribute信息
    override func prepareLayout() {
        if collectionView!.numberOfSections() == 0 {
            return
        }
         
        var column = 0
        var xOffset: CGFloat = 0
        var yOffset: CGFloat = 0
        var contentWidth: CGFloat = 0
        var contentHeight: CGFloat = 0
         
        if itemAttributes.count > 0 {
            return
        }
         
        itemAttributes = []
        itemsSize = []
         
        if itemsSize.count != viewController.cols.count {
            calculateItemsSize()
        }
         
        for var section = 0; section < collectionView?.numberOfSections(); section++ {
            var sectionAttributes: [UICollectionViewLayoutAttributes] = []
            for var index = 0; index < viewController.cols.count; index++ {
                let itemSize = itemsSize[index].CGSizeValue()
                 
                let indexPath = NSIndexPath(forItem: index, inSection: section)
                let attributes = UICollectionViewLayoutAttributes(forCellWithIndexPath:
                    indexPath)
                //除第一列,其它列位置都左移一个像素,防止左右单元格间显示两条边框线
                if index == 0{
                    attributes.frame = CGRectIntegral(CGRectMake(xOffset, yOffset,
                        itemSize.width, itemSize.height))
                }else {
                    attributes.frame = CGRectIntegral(CGRectMake(xOffset-1, yOffset,
                        itemSize.width+1, itemSize.height))
                }
                 
                sectionAttributes.append(attributes)
                 
                xOffset = xOffset+itemSize.width
                column++
                 
                if column == viewController.cols.count {
                    if xOffset > contentWidth {
                        contentWidth = xOffset
                    }
                     
                    column = 0
                    xOffset = 0
                    yOffset += itemSize.height
                }
            }
            itemAttributes.append(sectionAttributes)
        }
         
        let attributes = itemAttributes.last!.last! as UICollectionViewLayoutAttributes
        contentHeight = attributes.frame.origin.y + attributes.frame.size.height
        contentSize = CGSizeMake(contentWidth, contentHeight)
    }
     
    //需要更新layout时调用
    override func invalidateLayout() {
        itemAttributes = []
        itemsSize = []
        contentSize = CGSizeZero
        super.invalidateLayout()
    }
     
    // 返回内容区域总大小,不是可见区域
    override func collectionViewContentSize() -> CGSize {
        return contentSize
    }
     
    // 这个方法返回每个单元格的位置和大小
    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath)
        -> UICollectionViewLayoutAttributes? {
            return itemAttributes[indexPath.section][indexPath.row]
    }
     
    // 返回所有单元格位置属性
    override func layoutAttributesForElementsInRect(rect: CGRect)
        -> [UICollectionViewLayoutAttributes]? {
            var attributes: [UICollectionViewLayoutAttributes] = []
            for section in itemAttributes {
                attributes.appendContentsOf(section.filter(
                    {(includeElement: UICollectionViewLayoutAttributes) -> Bool in
                        return CGRectIntersectsRect(rect, includeElement.frame)
                }))
            }
            return attributes
    }
     
    //当边界发生改变时,是否应该刷新布局。
    //本例在宽度变化时,将重新计算需要的布局信息。
    override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
        let oldBounds = self.collectionView?.bounds
        if CGRectGetWidth(oldBounds!) != CGRectGetWidth(newBounds) {
            return true
        }else {
            return false
        }
    }
     
    //计算所有单元格的尺寸(每一列各一个单元格)
    func calculateItemsSize() {
        var remainingWidth = collectionView!.frame.width -
            collectionView!.contentInset.left - collectionView!.contentInset.right
         
        for var index = viewController.cols.count-1; index >= 0; index-- {
            let newItemSize = sizeForItemWithColumnIndex(index,
                remainingWidth: remainingWidth)
            remainingWidth -= newItemSize.width
            let newItemSizeValue = NSValue(CGSize: newItemSize)
            //由于遍历列的时候是从尾部开始遍历了,因此将结果插入数组的时候都是放人第一个位置
            itemsSize.insert(newItemSizeValue, atIndex: 0)
        }
    }
     
    //计算某一列的单元格尺寸
    func sizeForItemWithColumnIndex(columnIndex: Int, remainingWidth: CGFloat) -> CGSize {
        let columnString = viewController.cols[columnIndex]
        //根据列头标题文件,估算各列的宽度
        let size = NSString(string: columnString).sizeWithAttributes([
            NSFontAttributeName:UIFont.systemFontOfSize(15),
            NSUnderlineStyleAttributeName:NSUnderlineStyle.StyleSingle.rawValue
            ])
         
        //如果有剩余的空间则都给第一列
        if columnIndex == 0 {
            return CGSizeMake(max(remainingWidth, size.width + 17), size.height + 10)
        }
        //行高增加10像素,列宽增加17像素
        return CGSizeMake(size.width + 17, size.height + 10)
    }
}

--- ViewController.swift(测试类) ---

import UIKit
 
class ViewController: UIViewController {
     
    var gridViewController: UICollectionGridViewController!
     
    override func viewDidLoad() {
        super.viewDidLoad()
         
        gridViewController = UICollectionGridViewController()
        gridViewController.setColumns(["客户", "消费金额", "消费次数", "满意度"])
        gridViewController.addRow(["hangge", "100", "8", "60%"])
        gridViewController.addRow(["张三", "223", "16", "81%"])
        gridViewController.addRow(["李四", "143", "25", "93%"])
        gridViewController.addRow(["王五", "75", "2", "53%"])
        gridViewController.addRow(["韩梅梅", "43", "12", "33%"])
        gridViewController.addRow(["李雷", "33", "27", "45%"])
        gridViewController.addRow(["王大力", "33", "22", "15%"])
        view.addSubview(gridViewController.view)
    }
     
    override func viewDidLayoutSubviews() {
        gridViewController.view.frame = CGRectMake(0, 50, view.frame.width,
            view.frame.height-60)
    }
     
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }   
}

时间: 2024-10-30 08:19:19

Swift多列表格组件的基本功能的例子的相关文章

Swift多列表格组件的实现具备排序功能

1,支持排序的多列表格(multi-column sortable table control)效果图              2,功能说明: (1)表格列头文字增加下划线样式,表示可以点击. (2)点击列头标题,内容条目便会根据该列数据进行排序显示(先升序.后降序,依次交替) (3)排序列背景色会变为蓝色,同时列头会显示上下箭头表示排列顺序. (4)这里排序的上下箭头不是图片,而是使用 Font Awesome 图标字体库.优点是可以很轻松地设置颜色和大小,而不会失真   3,项目代码 (代

DBGridEh(增强型表格组件)功能详解

DBGRIDEH 是Enlib 3.0组件包中的组件之一.Enlib 3.0组件包是一位俄国人为增强Borland系列开发工具功能而开发的第三方组件,它具有界面友好.功能强大.开发效率高..快速制作预览/打印简单中国式报表等特点.因此,一推出即受到广大Borland程序员的青睐.目前这个版本支持Borland Delphi versions 4,5,6&7 和 Borland C++ Builder versions 4 & 5 ,可极大地提高数据库应用系统客户端的性能.许多商品软件如&l

easyui datagrid 表格组件列属性formatter和styler使用方法

 明确单元格DOM结构 要想弄清楚formatter和styler属性是怎么工作的,首先要弄清楚datagrid组件内容单元格的DOM接口,注意,这里指的是内容单元格,不包括标题单元格,标题单元格的结构有所区别.我们所有内容单元格的默认DOM结构如下: ? 1 2 3 4 5 <td field="code">     <div style="text-align:left" class="datagrid-cell datagrid

JS表格组件神器bootstrap table详解(基础版)_javascript技巧

一.Bootstrap Table的引入 关于Bootstrap Table的引入,一般来说还是两种方法: 1.直接下载源码,添加到项目里面来. 由于Bootstrap Table是Bootstrap的一个组件,所以它是依赖Bootstrap的,我们首先需要添加Bootstrap的引用. 2.使用我们神奇的Nuget 打开Nuget,搜索这两个包 Bootstrap已经是最新的3.3.5了,我们直接安装即可. 而Bootstrap Table的版本竟然是0.4,这也太坑爹了.所以博主建议Boot

JS组件系列之Bootstrap table表格组件神器【终结篇】_javascript技巧

bootstrap table系列: JS表格组件神器bootstrap table详解(基础版) JS组件系列之Bootstrap table表格组件神器[终结篇] JS组件系列之Bootstrap table表格组件神器[二.父子表和行列调序] Bootstrap Table是轻量级的和功能丰富的以表格的形式显示的数据,支持单选,复选框,排序,分页,显示/隐藏列,固定标题滚动表,响应式设计,Ajax加载JSON数据,点击排序的列,卡片视图等.那么本文给大家介绍JS组件系列之Bootstrap

JS组件系列——表格组件神器:bootstrap table(三:终结篇,最后的干货福利)

原文:JS组件系列--表格组件神器:bootstrap table(三:终结篇,最后的干货福利) 前言:前面介绍了两篇关于bootstrap table的基础用法,这章我们继续来看看它比较常用的一些功能,来个终结篇吧,毛爷爷告诉我们做事要有始有终~~bootstrap table这东西要想所有功能覆盖似乎不太现实,博主挑选了一些自认为比较常用的功能在此分享给各位园友.源码也在这篇统一给出.好了,不多说废话,开始我们的干货之旅吧. bootstrap table系列: JS组件系列--表格组件神器

JS组件系列——表格组件神器:bootstrap table(二:父子表和行列调序)

原文:JS组件系列--表格组件神器:bootstrap table(二:父子表和行列调序) 前言:上篇 JS组件系列--表格组件神器:bootstrap table 简单介绍了下Bootstrap Table的基础用法,没想到讨论还挺热烈的.有园友在评论中提到了父子表的用法,今天就结合Bootstrap table的父子表和行列调序的用法再来介绍下它稍微高级点的用法. bootstrap table系列: JS组件系列--表格组件神器:bootstrap table JS组件系列--表格组件神器

JS组件系列——表格组件神器:bootstrap table

原文:JS组件系列--表格组件神器:bootstrap table 前言:之前一直在忙着各种什么效果,殊不知最基础的Bootstrap Table用法都没有涉及,罪过,罪过.今天补起来吧.上午博主由零开始自己从头到尾使用了一遍Bootstrap Table ,遇到不少使用方面的问题,也做了一部分笔记,在此分享出来供需要使用的园友参考.还记得前两天有园友加群问我Bootstrap Table的使用问题,呵呵,巧了,今天博主也遇到同样的问题了,在此还是要表示抱歉,没有将这篇提前发出来. bootst

JS表格组件BootstrapTable行内编辑解决方案x-editable_javascript技巧

前言:之前介绍bootstrapTable组件的时候有提到它的行内编辑功能,只不过为了展示功能,将此一笔带过了,罪过罪过!最近项目里面还是打算将行内编辑用起来,于是再次研究了下x-editable组件,遇到过一些坑,再此做个采坑记录吧!想要了解bootstrapTable的朋友可以移步JS组件系列--表格组件神器:bootstrap table.  一.x-editable组件介绍  x-editable组件是一个用于创建可编辑弹出框的插件,它支持三种风格的样式:bootstrap.Jquery