Swift - 多列表格组件的实现(样例4:表格样式美化)
作者:hangge | 2017-05-15 08:10
相关文章系列:
Swift - 多列表格组件的实现(样例1:基本功能的实现)
Swift - 多列表格组件的实现(样例2:带排序功能)
Swift - 多列表格组件的实现(样例3:表头、列头固定)
[当前文章] Swift - 多列表格组件的实现(样例4:表格样式美化)
Swift - 多列表格组件的实现(样例1:基本功能的实现)
Swift - 多列表格组件的实现(样例2:带排序功能)
Swift - 多列表格组件的实现(样例3:表头、列头固定)
[当前文章] Swift - 多列表格组件的实现(样例4:表格样式美化)
在之前的几篇文章里,我主要演示如何使用 CollectionView 实现多列表格组件。而对于表格的样式没有特别设置,只是简单地加个单元格边框,看起来不太美观。
本文对表格样式做个美化,当然功能方面没有修改。
1,效果图
- 表头背景使用绿色,文字使用白色。
- 表格内容背景使用白色、灰色交替显示。
- 点击列头进行排序,同时该列内容单元格背景色变成淡蓝色。
- 去掉所有单元格的边框,使表格更加清爽。
2,功能说明
- 表头固定,上下拖动滚动条表头位置不变。
- 首列固定,左右拖动滚动条首列位置不变。
- 点击列头标题,内容条目便会根据该列数据进行排序显示(先升序、后降序,依次交替)
3,项目代码
--- UICollectionGridViewController.swift(组件类) ---
import Foundation import UIKit //表格排序协议 protocol UICollectionGridViewSortDelegate: class { func sort(colIndex: Int, asc: Bool, rows: [[Any]]) -> [[Any]] } //多列表格组件(通过CollectionView实现) class UICollectionGridViewController: UICollectionViewController { //表头数据 var cols: [String]! = [] //行数据 var rows: [[Any]]! = [] //排序代理 weak var sortDelegate: UICollectionGridViewSortDelegate? //选中的表格列(-1表示没有选中的) private var selectedColIdx = -1 //列排序顺序 private var asc = true init() { //初始化表格布局 let layout = UICollectionGridViewLayout() super.init(collectionViewLayout: layout) layout.viewController = self collectionView!.backgroundColor = UIColor.white collectionView!.register(UICollectionGridViewCell.self, forCellWithReuseIdentifier: "cell") collectionView!.delegate = self collectionView!.dataSource = self collectionView!.isDirectionalLockEnabled = 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: [Any]) { rows.append(row) collectionView!.collectionViewLayout.invalidateLayout() collectionView!.reloadData() } override func viewDidLoad() { super.viewDidLoad() } override func viewDidLayoutSubviews() { collectionView!.frame = CGRect(x:0, y:0, width:view.frame.width, height:view.frame.height) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } //返回表格总行数 override func numberOfSections(in 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, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! UICollectionGridViewCell //设置列头单元格,内容单元格的数据 if indexPath.section == 0 { cell.label.font = UIFont.systemFont(ofSize: 15, weight: UIFontWeightBold) cell.label.text = cols[indexPath.row] cell.label.textColor = UIColor.white } else { cell.label.font = UIFont.systemFont(ofSize: 15) cell.label.text = "\(rows[indexPath.section-1][indexPath.row])" cell.label.textColor = UIColor.black } //表头单元格背景色 if indexPath.section == 0 { cell.backgroundColor = UIColor(red: 0x91/255, green: 0xDA/255, blue: 0x51/255, alpha: 1) //排序列列头显示升序降序图标 if indexPath.row == selectedColIdx { let iconType = asc ? FAType.FALongArrowUp : FAType.FALongArrowDown cell.imageView.setFAIconWithName(icon: iconType, textColor: UIColor.white) }else{ cell.imageView.image = nil } } //内容单元格背景色 else { //排序列的单元格背景会变色 if indexPath.row == selectedColIdx { //排序列的单元格背景会变色 cell.backgroundColor = UIColor(red: 0xCC/255, green: 0xF8/255, blue: 0xFF/255, alpha: 1) } //数据区域每行单元格背景色交替显示 else if indexPath.section % 2 == 0 { cell.backgroundColor = UIColor(white: 242/255.0, alpha: 1) } else { cell.backgroundColor = UIColor.white } } return cell } //单元格选中事件 override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { //打印出点击单元格的[行,列]坐标 print("点击单元格的[行,列]坐标: [\(indexPath.section),\(indexPath.row)]") if indexPath.section == 0 && sortDelegate != nil { //如果点击的是表头单元格,则默认该列升序排列,再次点击则变降序排列,以此交替 asc = (selectedColIdx != indexPath.row) ? true : !asc selectedColIdx = indexPath.row rows = sortDelegate?.sort(colIndex: indexPath.row, asc: asc, rows: rows) collectionView.reloadData() } } }
--- UICollectionGridViewCell.swift(组件单元格类) ---
import UIKit class UICollectionGridViewCell: UICollectionViewCell { //内容标签 var label:UILabel! //箭头图标 var imageView:UIImageView! //标签左边距 var paddingLeft:CGFloat = 5 override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = UIColor.white self.clipsToBounds = true //添加内容标签 self.label = UILabel(frame: .zero) self.label.textAlignment = .center self.addSubview(self.label) //添加箭头图标 self.imageView = UIImageView(frame: .zero) self.addSubview(self.imageView) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() label.frame = CGRect(x: paddingLeft, y: 0, width: frame.width - paddingLeft * 2, height: frame.height) let imageWidth: CGFloat = 14 let imageHeight: CGFloat = 14 imageView.frame = CGRect(x:frame.width - imageWidth, y:frame.height/2 - imageHeight/2, width:imageWidth, height:imageHeight) } }
--- UICollectionGridViewLayout.swift(布局类) ---
import Foundation import UIKit //多列表格组件布局类 class UICollectionGridViewLayout: UICollectionViewLayout { //记录每个单元格的布局属性 private var itemAttributes: [[UICollectionViewLayoutAttributes]] = [] private var itemsSize: [NSValue] = [] private var contentSize: CGSize = CGSize.zero //表格组件视图控制器 var viewController: UICollectionGridViewController! //准备所有view的layoutAttribute信息 override func prepare() { 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 section in 0 ..< (collectionView?.numberOfSections)! { var sectionAttributes: [UICollectionViewLayoutAttributes] = [] for index in 0 ..< viewController.cols.count { let itemSize = itemsSize[index].cgSizeValue let indexPath = IndexPath(item: index, section: section) let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) //除第一列,其它列位置都左移一个像素,防止左右单元格间显示两条边框线 attributes.frame = CGRect(x:xOffset, y:yOffset, width:itemSize.width, height:itemSize.height).integral //将表头、首列单元格置为最顶层 if section == 0 && index == 0 { attributes.zIndex = 1024 }else if section == 0 || index == 0 { attributes.zIndex = 1023 } //表头单元格位置固定 if section == 0 { var frame = attributes.frame frame.origin.y = self.collectionView!.contentOffset.y attributes.frame = frame } //首列单元格位置固定 if index == 0 { var frame = attributes.frame frame.origin.x = self.collectionView!.contentOffset.x + collectionView!.contentInset.left attributes.frame = frame } sectionAttributes.append(attributes) xOffset = xOffset+itemSize.width column += 1 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 = CGSize(width:contentWidth, height:contentHeight) } //需要更新layout时调用 override func invalidateLayout() { itemAttributes = [] itemsSize = [] contentSize = CGSize.zero super.invalidateLayout() } // 返回内容区域总大小,不是可见区域 override var collectionViewContentSize: CGSize { get { return contentSize } } // 这个方法返回每个单元格的位置和大小 override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { return itemAttributes[indexPath.section][indexPath.row] } // 返回所有单元格位置属性 override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { var attributes: [UICollectionViewLayoutAttributes] = [] for section in itemAttributes { attributes.append(contentsOf: section.filter( {(includeElement: UICollectionViewLayoutAttributes) -> Bool in return rect.intersects(includeElement.frame) })) } return attributes } //改为边界发生任何改变时(包括滚动条改变),都应该刷新布局。 override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return true } //计算所有单元格的尺寸(每一列各一个单元格) func calculateItemsSize() { var remainingWidth = collectionView!.frame.width - collectionView!.contentInset.left - collectionView!.contentInset.right var index = viewController.cols.count-1 while index >= 0 { let newItemSize = sizeForItemWithColumnIndex(columnIndex: index, remainingWidth: remainingWidth) remainingWidth -= newItemSize.width let newItemSizeValue = NSValue(cgSize: newItemSize) //由于遍历列的时候是从尾部开始遍历了,因此将结果插入数组的时候都是放人第一个位置 itemsSize.insert(newItemSizeValue, at: 0) index -= 1 } } //计算某一列的单元格尺寸 func sizeForItemWithColumnIndex(columnIndex: Int, remainingWidth: CGFloat) -> CGSize { let columnString = viewController.cols[columnIndex] //根据列头标题文件,估算各列的宽度 let size = NSString(string: columnString).size(attributes: [ NSFontAttributeName:UIFont.systemFont(ofSize: 15), NSUnderlineStyleAttributeName:NSUnderlineStyle.styleSingle.rawValue ]) //修改成所有列都平均分配(但宽度不能小于90) let width = max(remainingWidth/CGFloat(columnIndex+1), 90) //计算好的宽度还要取整,避免偏移 return CGSize(width: ceil(width), height:size.height + 10) } }
--- ViewController.swift(测试类) ---
import UIKit class ViewController: UIViewController, UICollectionGridViewSortDelegate { var gridViewController: UICollectionGridViewController! override func viewDidLoad() { super.viewDidLoad() gridViewController = UICollectionGridViewController() gridViewController.setColumns(columns: ["编号","客户", "消费金额", "消费次数", "满意度"]) gridViewController.addRow(row: ["No.01","hangge", "100", "8", "60%"]) gridViewController.addRow(row: ["No.02","张三", "223", "16", "81%"]) gridViewController.addRow(row: ["No.03","李四", "143", "25", "93%"]) gridViewController.addRow(row: ["No.04","王五", "75", "2", "53%"]) gridViewController.addRow(row: ["No.05","韩梅梅", "43", "12", "33%"]) gridViewController.addRow(row: ["No.06","李雷", "33", "27", "45%"]) gridViewController.addRow(row: ["No.07","王大力", "33", "22", "15%"]) gridViewController.addRow(row: ["No.08","蝙蝠侠", "100", "8", "60%"]) gridViewController.addRow(row: ["No.09","超人", "223", "16", "81%"]) gridViewController.addRow(row: ["No.10","钢铁侠", "143", "25", "93%"]) gridViewController.addRow(row: ["No.11","灭霸", "75", "2", "53%"]) gridViewController.addRow(row: ["No.12","快银", "43", "12", "33%"]) gridViewController.addRow(row: ["No.13","闪电侠", "33", "27", "45%"]) gridViewController.addRow(row: ["No.14","绿箭", "33", "22", "15%"]) gridViewController.addRow(row: ["No.15","绿巨人", "223", "16", "81%"]) gridViewController.addRow(row: ["No.16","黑寡妇", "143", "25", "93%"]) gridViewController.addRow(row: ["No.17","企鹅人", "75", "2", "53%"]) gridViewController.addRow(row: ["No.18","双面人", "43", "12", "33%"]) gridViewController.addRow(row: ["No.19","奥特曼", "33", "27", "45%"]) gridViewController.addRow(row: ["No.20","小怪兽s", "33", "22", "15%"]) gridViewController.sortDelegate = self view.addSubview(gridViewController.view) } override func viewDidLayoutSubviews() { gridViewController.view.frame = CGRect(x:0, y:64, width:view.frame.width, height:view.frame.height-64) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } //表格排序函数 func sort(colIndex: Int, asc: Bool, rows: [[Any]]) -> [[Any]] { let sortedRows = rows.sorted { (firstRow: [Any], secondRow: [Any]) -> Bool in let firstRowValue = firstRow[colIndex] as! String let secondRowValue = secondRow[colIndex] as! String if colIndex == 0 || colIndex == 1 { //首例、姓名使用字典排序法 if asc { return firstRowValue < secondRowValue } return firstRowValue > secondRowValue } else if colIndex == 2 || colIndex == 3 { //中间两列使用数字排序 if asc { return Int(firstRowValue)! < Int(secondRowValue)! } return Int(firstRowValue)! > Int(secondRowValue)! } //最后一列数据先去掉百分号,再转成数字比较 let firstRowValuePercent = Int(firstRowValue.substring(to: firstRowValue.index(before: firstRowValue.endIndex)))! let secondRowValuePercent = Int(secondRowValue.substring(to: secondRowValue.index(before: secondRowValue.endIndex)))! if asc { return firstRowValuePercent < secondRowValuePercent } return firstRowValuePercent > secondRowValuePercent } return sortedRows } }源码下载:hangge_1090.zip
全部评论(2)
如果我中间加个demoVC,从demoVC push到你写的表格控制器,然pop回来
会导致内存泄露
站长回复:谢谢你的提醒,之前的代码确实会有这个问题,现已修正。
航哥您好,在新系统iOS11下表格显示有问题,怎么解决?
站长回复:我测试了下正常啊,不知你那边显示的是什么问题?