iOS开发tips-神奇的UITableView

概述

UITableView是iOS开发中使用频率最高的UI控件,在前面的文章中对于UITableView的具体用法有详细的描述,今天主要看一些UITableView开发中的常见一些坑,这些坑或许不深,但是如果开发中注意不到的话往往比较浪费时间。

神奇的section header

事情的起因是一个网友说要实现一个类似下图界面,但是不管是设置sectionHeaderHeight还是代理方法中实现func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int)都无法调整Section Header的默认高度。而且他还试过通过func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?自定义一个header也是无济于事。

?

其实这个问题解决起来并不复杂,只要设置sectionFooterHeight为0即可(当然对应通过代理方法也是可以的)。默认情况下分组样式UITableView的section header和section footer是由一个默认高度的,并不为0。

import UIKit

private let ProfileTableViewControllerCellReuseIdentifier = "ProfileTableViewCell"
class ProfileTableViewController: UIViewController {

    // MARK: - Nested type
    struct ProfileData {
        var title:String!
        var content:String!
    }

    // MARK: - TableView life cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        self.setup()
        self.loadData()
    }

    // MARK: - Private method
    private func setup() {
        self.view.backgroundColor = UIColor.gray
        self.tableView.register(ProfileTableViewCell.self, forCellReuseIdentifier: ProfileTableViewControllerCellReuseIdentifier)
        self.tableView.dataSource = self
        self.tableView.delegate = self

        self.view.addSubview(self.tableView)
        self.tableView.snp.makeConstraints { (make) in
            make.edges.equalTo(0.0)
        }

    }

    private func loadData() {
        self.data.removeAll()

        let row1 = ProfileData(title: "Name", content: "Kenshin Cui")
        let row2 = ProfileData(title: "ID", content: "kenshincui")
        let section1 = [row1,row2]

        let row3 = ProfileData(title: "Gender", content: "Male")
        let row4 = ProfileData(title: "Region", content: "China")
        let section2 = [row3,row4]

        let row5 = ProfileData(title: "What‘s Up", content: "We‘re here to put a dent in the universe。 Otherwise why else even be here?")
        let section3 = [row5]

        self.data.append(section1)
        self.data.append(section2)
        self.data.append(section3)

        self.tableView.reloadData()
    }

    // MARK: - Private property
    private lazy var tableView:UITableView = {
        let temp = UITableView(frame: CGRect.zero, style: .grouped)
        temp.estimatedRowHeight = 50
        temp.sectionFooterHeight = 0
        return temp
    }()

    fileprivate var data = [[ProfileData]]()

}

extension ProfileTableViewController:UITableViewDataSource, UITableViewDelegate{
    // MARK: - Table view data source
    func numberOfSections(in tableView: UITableView) -> Int {
        return self.data.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let sectionData = self.data[section]
        return sectionData.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: ProfileTableViewControllerCellReuseIdentifier, for: indexPath) as? ProfileTableViewCell {
            let dataItem = self.data[indexPath.section][indexPath.row]
            cell.title = dataItem.title
            cell.content = dataItem.content
            return cell
        }
        return UITableViewCell()
    }

    // MARK: - Table view delegate
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        if section == 0 {
            return CGFloat.leastNormalMagnitude
        }
        return 8.0
    }
}

数据源和代理方法的自动调用

同样是上面的代码,如果去掉loadData()方法后面的reloadData()调用你会发现整个界面不会有任何异常现象,数据可以照样加载,也就是说数据源方法和代理方法会正常调用加载对应的数据。这是为什么呢?

事实上类似于func numberOfSections(in tableView: UITableView) -> Int等方法并不是只有reloadData()等方法刷新的时候才会调用,而是在已经设置了dataSource和delegate后布局变化后就会调用。因此即使注释掉上面的reloadData()方法界面仍然不会有变化。

要验证这个结论可以延迟设置数据源和代理(注意:手动更新界面布局setNeedsLayoutlayoutIfNeeded),或者最简单的方式就是旋转屏幕会发现func numberOfSections(in tableView: UITableView) -> Intfunc tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat等方法都会调用。除此之外,你也会发现func numberOfSections(in tableView: UITableView) -> Int其实并不止一次调用,系统内部为了确定整个布局本身对它就存在着调用。

分割线左侧对齐

貌似苹果的设计分割就建议你将分割线左侧留出一定的间距,但在实际开发过程中却很少见设计师会这么做。要让分割线左侧对齐对于当前iOS 10来说应该是再简单不过,只要设置UITableView的separatorInset = UIEdgeInsets.zero即可(如果仅仅想要控制器某个UITableViewCell的分割线则直接设置UITableViewCell的separatorInset,当然这种方式对于.grouped风格的UITableView而言无法修改Section顶部和底部分割线)。不过低版本的iOS就要复杂一些,例如iOS 7除了以上设置还要设置UITableViewCell的separatorInset;而iOS 8、9还要再设置UITableView的layoutMargins等。

当然,如果你希望控制右侧的间距,仍然可以调整separatorInset的right即可,不过调整top、bottom应该不会生效。

移除多余的行

不妨将上面代码修改为.plain风格的UITableView,此时会发现由于数据后面多了很多多余的空行。移除这些空行的方法也很简单,那就是设置tableFooterView = UIView()即可(如果设置view的高度为CGFloat.leastNormalMagnitude则不显示最后面的一条分割线)。当然,默认情况下style为.plain则每个section的上下均不存在分割线。

注意:.plain风格的UITableView设置sectionFooterHeight不起作用,必须通过func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat代理方法设置。

.plain下禁用section header悬停

有时候你不得不用.plain Style,因为这样一来默认Section Header就有悬停效果,但是有时候你只是想要使用.plain样式却不想显示悬停效果,这时你必须禁用这个效果。

当然可以使用很多黑科技来禁用悬停效果,但是最简单的方式应该是直接将header隐藏起来:通过设置tableViewHeader高度等同于section header的高度,然后设置tableView的contentInset让它偏移到上方,这样一来当滚动到section header悬浮时出现的位置不是0而是被隐藏起来的偏移位置。

import UIKit

private let ProfileTableViewControllerCellReuseIdentifier = "ProfileTableViewCell"
class ProfileTableViewControllerWithPlain: UIViewController {

    // MARK: - Nested type
    struct ProfileData {
        var title:String!
        var content:String!
    }

    // MARK: - TableView life cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        self.setup()
        self.loadData()
    }

    // MARK: - Private method
    private func setup() {
        self.view.backgroundColor = UIColor.gray
        self.tableView.register(ProfileTableViewCell.self, forCellReuseIdentifier: ProfileTableViewControllerCellReuseIdentifier)
        self.tableView.estimatedRowHeight = 50
        self.tableView.dataSource = self
        self.tableView.delegate = self

        self.view.addSubview(self.tableView)
        self.tableView.snp.makeConstraints { (make) in
            make.edges.equalTo(0.0)
        }

        // disable section header sticky
        let headerView = UIView(frame: CGRect(x: 0, y: 0, width: 375, height: 8.0))
        self.tableView.tableHeaderView = headerView
        self.tableView.contentInset.top = -8.0

    }

    private func loadData() {
        self.data.removeAll()

        let row1 = ProfileData(title: "Name", content: "Kenshin Cui")
        let row2 = ProfileData(title: "ID", content: "kenshincui")
        let section1 = [row1,row2]

        let row3 = ProfileData(title: "Gender", content: "Male")
        let row4 = ProfileData(title: "Region", content: "China")
        let section2 = [row3,row4]

        let row5 = ProfileData(title: "What‘s Up", content: "We‘re here to put a dent in the universe。 Otherwise why else even be here?")
        let section3 = [row5]

        self.data.append(section1)
        self.data.append(section2)
        self.data.append(section3)

//        self.tableView.reloadData()
    }

    // MARK: - Private property
    fileprivate lazy var tableView:UITableView = {
        let temp = UITableView(frame: CGRect.zero, style: .plain)
        temp.estimatedRowHeight = 50
        temp.tableFooterView = UIView()
        return temp
    }()

    fileprivate var data = [[ProfileData]]()

}

extension ProfileTableViewControllerWithPlain:UITableViewDataSource, UITableViewDelegate{
    // MARK: - Table view data source
    func numberOfSections(in tableView: UITableView) -> Int {
        return self.data.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let sectionData = self.data[section]
        return sectionData.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: ProfileTableViewControllerCellReuseIdentifier, for: indexPath) as? ProfileTableViewCell {
            let dataItem = self.data[indexPath.section][indexPath.row]
            cell.title = dataItem.title
            cell.content = dataItem.content
            return cell
        }
        return UITableViewCell()
    }

    // MARK: - Table view delegate
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        if section == 0 {
            return CGFloat.leastNormalMagnitude
        }
        return 8.0
    }

}

调整tableHeaderView的高度

如果你的UITableView设置了tableHeaderView的话,有事你可能喜欢动态调整tableHeaderView的高度,但是怎么修改这个view的高度都不会生效。正确的修改方法是受限修改view的高度,然后将这个view重新设置给UITableView的tableHeaderView属性。下面的demo演示了这一过程,通过这个demo可以看到一个有趣的事实:如果你设置了tableHeaderView但是没有指定高度的话,UITableView会自动给他提供一个默认高度。这在低版本的iOS系统中即使不指定tableHeaderView也会有这个一个默认高度,解决方式就是设置view的高度为一个极小值,当然iOS 10中如果不指定则默认没有tableHeaderView。

import UIKit

private let ProfileTableViewControllerCellReuseIdentifier = "ProfileTableViewCell"
class ProfileTableViewControllerWithHeader: UIViewController {

    // MARK: - Nested type
    struct ProfileData {
        var title:String!
        var content:String!
    }

    // MARK: - TableView life cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        self.setup()
        self.loadData()
        self.loadHeaderData()
    }

    // MARK: - Private method
    private func setup() {
        self.view.backgroundColor = UIColor.gray
        self.tableView.tableHeaderView = self.headerView
        self.tableView.register(ProfileTableViewCell.self, forCellReuseIdentifier: ProfileTableViewControllerCellReuseIdentifier)
        //        self.tableView.separatorInset = UIEdgeInsets.zero
        self.tableView.dataSource = self
        self.tableView.delegate = self

        self.view.addSubview(self.tableView)
        self.tableView.snp.makeConstraints { (make) in
            make.edges.equalTo(0.0)
        }

    }

    private func loadData() {
        self.data.removeAll()

        let row1 = ProfileData(title: "Name", content: "Kenshin Cui")
        let row2 = ProfileData(title: "ID", content: "kenshincui")
        let section1 = [row1,row2]

        let row3 = ProfileData(title: "Gender", content: "Male")
        let row4 = ProfileData(title: "Region", content: "China")
        let section2 = [row3,row4]

        let row5 = ProfileData(title: "What‘s Up", content: "We‘re here to put a dent in the universe。 Otherwise why else even be here?")
        let section3 = [row5]

        self.data.append(section1)
        self.data.append(section2)
        self.data.append(section3)

    }

    private func loadHeaderData() {
        DispatchQueue.main.asyncAfter(
        deadline: DispatchTime.now() + Double(Int64(2.0 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) {
            () -> Void in
            // set header data
            self.headerView.avatarURL = "avatar.jpg"
            self.headerView.introduction = "即使是别人看不见的地方,对其工艺也应该尽心尽力!"
            self.headerView.frame.size.height = self.headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
            self.tableView.tableHeaderView = self.headerView
        }
    }

    // MARK: - Private property
    private lazy var tableView:UITableView = {
        let temp = UITableView(frame: CGRect.zero, style: .grouped)
        temp.estimatedRowHeight = 50
        temp.sectionFooterHeight = 0
        return temp
    }()

    private lazy var headerView:ProfileHeaderView = {
        let temp = ProfileHeaderView()
        return temp
    }()

    fileprivate var data = [[ProfileData]]()
}

extension ProfileTableViewControllerWithHeader:UITableViewDataSource, UITableViewDelegate{
    // MARK: - Table view data source
    func numberOfSections(in tableView: UITableView) -> Int {
        return self.data.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let sectionData = self.data[section]
        return sectionData.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: ProfileTableViewControllerCellReuseIdentifier, for: indexPath) as? ProfileTableViewCell {
            let dataItem = self.data[indexPath.section][indexPath.row]
            cell.title = dataItem.title
            cell.content = dataItem.content
            return cell
        }
        return UITableViewCell()
    }

    // MARK: - Table view delegate
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        if section == 0 {
            return CGFloat.leastNormalMagnitude
        }
        return 8.0
    }

}
时间: 2024-08-02 10:52:36

iOS开发tips-神奇的UITableView的相关文章

iOS开发UI篇—实现UItableview控件数据刷新

iOS开发UI篇—实现UItableview控件数据刷新 一.项目文件结构和plist文件 二.实现效果 1.说明:这是一个英雄展示界面,点击选中行,可以修改改行英雄的名称(完成数据刷新的操作). 运行界面: 点击选中行: 修改数据后自动刷新: 三.代码示例 数据模型部分: YYheros.h文件 // // YYheros.h // 10-英雄展示(数据刷新) // // Created by apple on 14-5-29. // Copyright (c) 2014年 itcase. A

iOS开发- iOS7显示偏差(UITableView下移)解决办法

之前碰到过一个问题. 就是利用storyboard拖动出来的控件, 在iOS7上跑老是莫名的下移. 比如这样(红色区域为多余的) 解决办法: iOS7在Conttoller中新增了这个属性: automaticallyAdjustsScrollViewInsets,当设置为YES时(默认YES),如果视图里面存在唯一一个UIScrollView或其子类View,那么它会自动设置相应的内边距,这样可以让scroll占据整个视图,又不会让导航栏遮盖. 我们设置automaticallyAdjusts

IOS开发之表视图(UITableView)

IOS开发之表视图(UITableView)的基本介绍(一) (一):UITableView的基本概念 1.在IOS开发中,表视图的应用十分广泛和普及.因此掌握表视图的用法显得非常重要.一般情况下对于数据的展示 我们都会选择表视图,比如通讯录和一些数据列表. 2.我们可以选择创建表视图也可以创建表视图控制器. (二)UITableView基本样式如下(1:UITableViewStylePlain(普通表视图),2:UITableViewStyleGroup(分组表视图)): (三)UITabl

iOS开发tips总结

tip 1 :  给UIImage添加毛玻璃效果 func blurImage(value:NSNumber) -> UIImage { let context = CIContext(options:[KCIContextUseSoftwareRenderer:true]) let ciImage = CoreImage.CIImage(image:self) let blurFilter = CIFilter(name:"CIGassianBlur") blurFilter?

iOS开发——项目实战总结&UITableView性能优化与卡顿问题

UITableView性能优化与卡顿问题 1.最常用的就是cell的重用, 注册重用标识符 如果不重用cell时,每当一个cell显示到屏幕上时,就会重新创建一个新的cell 如果有很多数据的时候,就会堆积很多cell.如果重用cell,为cell创建一个ID 每当需要显示cell 的时候,都会先去缓冲池中寻找可循环利用的cell,如果没有再重新创建cell 2.避免cell的重新布局 cell的布局填充等操作 比较耗时,一般创建时就布局好 如可以将cell单独放到一个自定义类,初始化时就布局好

IOS开发——UI进阶篇—UITableView,索引条,汽车数据展示案例

一.什么是UITableView 在iOS中,要实现展示列表数据,最常用的做法就是使用UITableViewUITableView继承自UIScrollView,因此支持垂直滚动,而且性能极佳 UITableView的两种样式UITableViewStylePlainUITableViewStyleGrouped 二.如何展示数据 UITableView需要一个数据源(dataSource)来显示数据 UITableView会向数据源查询一共有多少行数据以及每一行显示什么数据等 没有设置数据源的

iOS开发UI篇—在UItableview中实现加载更多功能

一.实现效果 点击加载更多按钮,出现一个加载图示,三秒钟后添加两条新的数据.                      二.实现代码和说明 当在页面(视图部分)点击加载更多按钮的时候,主页面(主控制器)会加载两条数据进来. 视图部分的按钮被点击的时候,要让主控制器加载数据,刷新表格,2B青年会在视图中增加一个主控制器的属性,通过这个属性去调用进行加载,但在开发中通常通过代理模式来完成这个操作. 下面分别是两种实现的代码. 1.项目结构和说明 说明:加载更多永远都放在这个tableview的最下端

iOS开发——实战项目总结&UITableView性能优化技巧

UITableView性能优化技巧 Table view需要有很好的滚动性能,不然用户会在滚动过程中发现动画的瑕疵. 为了保证table view平滑滚动,确保你采取了以下的措施: 正确使用`reuseIdentifier`来重用cells 尽量使所有的view opaque,包括cell自身 避免渐变,图片缩放,后台选人 缓存行高 如果cell内现实的内容来自web,使用异步加载,缓存请求结果 使用`shadowPath`来画阴影 减少subviews的数量 尽量不适用`cellForRowA

【iOS开发之旅】UITableView示例-LOL英雄列表

UITableView示例-LOL英雄列表运行效果        CZHero.h // // CZHero.h // 04-UITableView示例-加载plist文件 // // Created by ChenQianPing on 16/1/21. // Copyright © 2016年 chenqp. All rights reserved. // #import <Foundation/Foundation.h> @interface CZHero : NSObject // 头