(作业)TableView、Delegate、DataSource
来源:互联网 发布:魔兽争霸3mac怎么安装 编辑:程序博客网 时间:2024/05/19 23:52
来来来,继续作业系列的博客,老规矩了,直接上要求
这次作业涉及到iOS开发中最常用的一个UI组件UITableView,App Store中的几乎所有工具型和应用型App都使用了UITableView,而初学UITableView又不是那么容易,因为它涉及到很多代理和数据源的问题,但用熟练了,会觉得它非常地方便。
废话不多说了,先来完成要求吧,在这个过程中,博主会讲解一些细节方面的东西。
首先是需要用到作业3中的类,点击这里,看看细节。我这里就不多说了,直接把全部代码贴出来吧。各位最好新建一个文件来放这个类,不然会使ViewController显得非常臃肿。
Person.swift
import Foundation//性别的枚举enum Gender: Int { case male //男性 case female //女性 case unknow //未知 //重载>操作符,方便后面排序使用 static func >(lhs: Gender, rhs: Gender) -> Bool { return lhs.rawValue < rhs.rawValue }}//公寓的枚举enum Department { case one, two, three}//学校协议protocol SchoolProtocol { var department: Department { get set } func lendBook()}//人类class Person: CustomStringConvertible { var firstName: String //姓 var lastName: String //名 var age: Int //年龄 var gender: Gender //性别 var fullName: String { //全名 get { return firstName + lastName } } //构造方法 init(firstName: String, lastName: String, age: Int, gender: Gender) { self.firstName = firstName self.lastName = lastName self.age = age self.gender = gender } convenience init(firstName: String, age: Int, gender: Gender) { self.init(firstName: firstName, lastName: "", age: age, gender: gender) } convenience init(firstName: String) { self.init(firstName: firstName, age: 0, gender: Gender.unknow) } required convenience init() { self.init(firstName: "") } //重载== static func ==(lhs: Person, rhs: Person) -> Bool { return lhs.fullName == rhs.fullName && lhs.age == rhs.age && lhs.gender == rhs.gender } //重载!= static func !=(lhs: Person, rhs: Person) -> Bool { return !(lhs == rhs) } //实现CustomStringConvertible协议中的计算属性,可以使用print直接输出对象内容 var description: String { return "fullName: \(self.fullName), age: \(self.age), gender: \(self.gender)" } //输出Person XXX is running func run() { print("Person \(self.fullName) is running") }}//教师类class Teacher: Person, SchoolProtocol { var title: String //标题 var department: Department //公寓 //构造方法 init(title: String, firstName: String, lastName: String, age: Int, gender: Gender, department: Department) { self.title = title self.department = department super.init(firstName: firstName, lastName: lastName, age: age, gender: gender) } init(title: String, department: Department) { self.title = title self.department = department super.init(firstName: "", lastName: "", age: 0, gender: .unknow) } convenience required init() { self.init(title: "", department: Department.one) } //重写父类的计算属性 override var description: String { return "title: \(self.title), fullName: \(self.fullName), age: \(self.age), gender: \(self.gender), department: \(self.department)" } //重载父类run方法 override func run() { print("Teacher \(self.fullName) is running") } //遵循协议的方法 func lendBook() { print("Teacher \(self.fullName) lend a book") }}//学生类class Student: Person, SchoolProtocol { var stuNo: Int //学号 var department: Department //公寓 //构造方法 init(stuNo: Int, firstName: String, lastName: String, age: Int, gender: Gender, department: Department) { self.stuNo = stuNo self.department = department super.init(firstName: firstName, lastName: lastName, age: age, gender: gender) } convenience init(stuNo: Int, firstName: String, lastName: String, age: Int, gender: Gender) { self.init(stuNo: stuNo, firstName: firstName, lastName: lastName, age: age, gender: gender, department: .one) } init(stuNo: Int, department: Department) { self.stuNo = stuNo self.department = department super.init(firstName: "", lastName: "", age: 0, gender: Gender.unknow) } required convenience init() { self.init(stuNo: 0, department: .one) } //重写父类的计算属性 override var description: String { return "stuNo: \(self.stuNo), fullName: \(self.fullName), age: \(self.age), gender: \(self.gender), department: \(self.department)" } //重载父类run方法 override func run() { print("Student \(self.fullName) is running") } //遵循协议的方法 func lendBook() { print("Teacher \(self.fullName) lend a book") }}
接下来就开始进入主题
为了方便我们对界面的布局,我们使用UINavigationController来放置按钮,所以需要在AppDelegate.swift的application(_:didFinishLaunchingWithOptions:)方法中添加下面这句代码
self.window?.rootViewController = UINavigationController(rootViewController: ViewController())
这句代码的作用就是将UIWindow的根视图控制器设置为UINavigationController并将该导航栏控制器的根视图控制器设置为ViewController。这样我们的ViewController就放置在导航栏控制器中了。
然后我们需要在ViewController中做如下几个属性的声明
//学生数组var students = [Student]()//教师数组var teachers = [Teacher]()//定义表头数组var tableTitle = ["Teacher", "Student"]//定义一个表视图var table: UITableView!//右边按钮var rightItem: UIBarButtonItem!//弹出框var alert: UIAlertController!
这些在下面的代码中会使用到,大致的作用看注释应该差不多能理解了。
然后我们需要创建数据,并按要求排序
//生成3个Teacher对象 for i in 1...3 { let temp = Teacher(title: "教授", firstName: "张", lastName: "\(i)", age: 21, gender: .female, department: .one) teachers.append(temp) } //生成4个Student对象 for i in 1..<5 { let temp = Student(stuNo: 2015110100 + i, firstName: "李", lastName: "\(i)", age: 19, gender: .male, department: .two) students.append(temp) } //按全名排序 teachers.sort { return $0.fullName < $1.fullName } students.sort { return $0.fullName < $1.fullName }
在准备好数据之后,我们就需要创建表视图了
//创建表视图,并设置代理和数据源table = UITableView(frame: self.view.bounds)table.delegate = selftable.dataSource = selfself.view.addSubview(table)
然后,我们在导航栏控制器上添加两个按钮,左边添加一个“添加”按钮,用于添加学生;右边添加“编辑”按钮,用于对表视图进行编辑操作。
//导航栏控制器右边的按钮 rightItem = UIBarButtonItem(title: "编辑", style: .plain, target: self, action: #selector(edit)) self.navigationItem.rightBarButtonItem = rightItem //导航栏控制器左边的按钮 let leftItem = UIBarButtonItem(title: "添加", style: .plain, target: self, action: #selector(addStudent)) self.navigationItem.leftBarButtonItem = leftItem
我们添加的“编辑”按钮在开始时声明,是因为我们需要更改它的title属性,当我们进入编辑状态时,需要将它改为“完成”;当它退出编辑状态时,需要将其改为“编辑”。
/// 编辑表视图 @objc func edit() { if table.isEditing { rightItem.title = "编辑" table.isEditing = false } else { rightItem.title = "完成" table.isEditing = true } }
“编辑”按钮的功能做完之后,就需要设置“添加”按钮的功能了。按要求需要输入相应的学生信息,我们这里就使用UIAlertController来弹出提示框,让用户输入相应的信息。
/// 添加学生提示框 @objc func addStudent() { alert = UIAlertController(title: "hh", message: "ss", preferredStyle: .alert) alert.addTextField { (textField) in textField.placeholder = "学生学号" } alert.addTextField { (textField) in textField.placeholder = "学生姓" } alert.addTextField { (textField) in textField.placeholder = "学生名" } alert.addTextField { (textField) in textField.placeholder = "学生性别" } alert.addTextField { (textField) in textField.placeholder = "学生年龄" } let OKBtn = UIAlertAction(title: "确定", style: .default) { (alert) in self.add() } let cancelBtn = UIAlertAction(title: "取消", style: .cancel, handler: nil) alert.addAction(OKBtn) alert.addAction(cancelBtn) self.present(alert, animated: true, completion: nil) } /// 添加学生 func add() { let no = Int(alert.textFields![0].text!) let firstName = alert.textFields![1].text! let lastName = alert.textFields![2].text! let gender: Gender switch alert.textFields![3].text! { case "男": gender = .male case "女": gender = .female default: gender = .unknow } let age = Int(alert.textFields![4].text!) let student = Student(stuNo: no!, firstName: firstName, lastName: lastName, age: age!, gender: gender) students.append(student) table.reloadData() }
我们先初始化一个alert型的UIAlertController,除了alert,常用的还有actionSheet,读者可以自行百度区别。然后向alert里面添加了5个UITextField用于用户输入学生信息,然后添加两个按钮,一个“确定”,一个“取消”,功能就不说了。注意UIAlertController上的按钮是UIAlertAction,而不是UIButton。在初始化UIAlertController时可以添加事件在按钮上,我们在“确定”按钮上添加了一个函数,用于将用户填的信息加入到表视图中。最后调用UIViewController的present方法将提示框弹出即可。
在add()方法中,我们将UITextField中的内容一一取出并做相关的处理,即可创建一个学生的实例,然后添加到学生数组中。这时我们只更新的数据,而界面(也就是表视图)并没有更新,所以我们需要让表视图重新加载一次数据。(博主并没有做输入的验证,各位读者可以自行扩展)。
在做完这些基础的操作之后,我们的数据和功能就已经准备完成了,接下来就要进行表视图的操作了。在最开始初始化表视图时,我们指定了其代理和数据源,这是系统定义个两个协议,通过这两个协议,我们可以很方便地对表视图进行操作和调整。所以我们需要在类的声明部分遵循UITableViewDelegate协议和UITableViewDataSource协议。截图如下:
UITableViewDelegate协议没有必须要实现的方法,而UITableViewDataSource中有两个方法必须要实现。
第一个是指定表视图中单元格行数的方法
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if section == 0 { return teachers.count } else { return students.count } }
因为我们这里有两种不同的数据信息需要展示,所以博主做了两个section(部分),分别返回两个section的行数即可。
第二个是指定表视图中的单元格的方法
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let identifier = tableTitle[indexPath.section] var cell = tableView.dequeueReusableCell(withIdentifier: identifier) if cell == nil { let style: UITableViewCellStyle = (identifier == "Teacher") ? .subtitle : .default cell = UITableViewCell(style: style, reuseIdentifier: identifier) cell?.accessoryType = .disclosureIndicator } switch identifier { case "Teacher": cell?.textLabel?.text = teachers[indexPath.row].fullName cell?.detailTextLabel?.text = teachers[indexPath.row].title case "Student": cell?.textLabel?.text = students[indexPath.row].fullName default: break } return cell! }
在这个方法中,我们先初始化了一个identifier常量,这个常量用于单元格的重用。在表视图中,往往有很多的单元格,但如果我们每滚动到一个单元格的位置时都要将其初始化的话,这样就会是界面卡顿,并且浪费很多内存,所以我们需要优化一下表视图,而系统刚好就提供了这种优化的方法,那就是注册单元格的重用。根据不同的identifier可以重用不同的单元格,在表视图滚动时,只需要更改单元格展示的数据即可,这样就可以大大的提高效率。
重用的机制是将已经初始化的单元格放入队列中,待需要时将其取出,更新数据。所以我们要使用dequeueReusableCell(withIdentifier:)方法从队列中取出单元格。但我们第一次进入界面时,队列中并没有单元格,所以我们取出的cell会是空的,我们就可以在这里初始化cell,并设置其样式。当cell取出后,就可以将数据展示在上面了,最后返回cell即可。
因为我们这里有两个部分的数据,所以我们需要指定section数
func numberOfSections(in tableView: UITableView) -> Int { return tableTitle.count }
然后为了用户方便,我们可以指定每一个section的头,来提示一些信息
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return tableTitle[section] }
做完这一些操作之后,我们就可以看到界面上展示的效果了。运行程序看看
初始界面
点击“添加”按钮
点击“编辑”按钮
到这里我们发现,我们点击“添加”按钮后输入信息,并点击“确定”,新添加的学生信息可以添加到表视图中,但我们点击编辑按钮后,却删除不了单元格,那是因为我们还缺少一些必要的设置。
首先我们需要使用代理设置每一个单于格的编辑类型,因为我们已经做了添加按钮,所以我们直接指定每一个单元格都是删除
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle { return .delete }
我们可以更改一下滑动出来的删除按钮显示的文字
func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? { return "删除" }
在做完这些之后,我们还是不能删除单元格,那是因为还需要一个方法来真正的删除数据
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == UITableViewCellEditingStyle.delete { if indexPath.section == 0 { teachers.remove(at: indexPath.row) } else { students.remove(at: indexPath.row) } tableView.deleteRows(at: [indexPath], with: .left) } }
在这个方法中,我们需要先判断编辑的类型,当为delete时,先删除section对应的数组中的数据,然后再删除表视图中的cell。删除cell使用deleteRows(at:with:)方法可以指定删除时的动画效果。
到这里,我们就可以删除单元格了。这时我们有了添加和删除的功能,但我们需要实现点击单元格给出选择反馈和移动cell。
首先实现点击单于给出反馈,我们做成点击某个cell后,弹出一个提示框,提示框中显示选中的是学生还是教师,并显示其名字。
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let category = tableTitle[indexPath.section] let name: String if indexPath.section == 0 { name = teachers[indexPath.row].fullName } else { name = students[indexPath.row].fullName } let message = "you selected \(category), name: \(name)" let alert = UIAlertController(title: "系统提示", message: message, preferredStyle: .alert) let OKBtn = UIAlertAction(title: "确定", style: .default, handler: nil) alert.addAction(OKBtn) self.present(alert, animated: true, completion: nil) }
做完这个之后,我们最后就需要实现单元格的移动了
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { if sourceIndexPath.section != destinationIndexPath.section { tableView.reloadData() } else { if sourceIndexPath.section == 0 { teachers.insert(teachers.remove(at: sourceIndexPath.row), at: destinationIndexPath.row) } else { students.insert(students.remove(at: sourceIndexPath.row), at: destinationIndexPath.row) } } }
在这个方法中,我们限制了单元格只能在自己的section里面移动,一旦跨section移动,那就会回到原来的位置。我们只要实现了这个方法,就可以将单元格移动了,但相应的,我们需要更改数据数组中相应元素的位置,这样才不会引起错误。
到这里,我们就基本完成了作业的要求,运行看一下效果。
添加学生信息
删除单元格
在Swift4.0之后,我们实现了删除功能之后,可以不需要点击“编辑”按钮进入编辑状态再删除单元格了,我们可以在cell上向左滑动,就会有删除按钮出现
最后是移动单于格了。
我们先进入编辑状态,然后点击右边的三根横线的按钮,就可以拖动单元格了。
到这里,本次作业完成,各位读者如果需要完善功能,可以在代码中进行改进,下面附上这次作业的ViewController的所有代码。
ViewConroller.swift
import UIKitclass ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { //学生数组 var students = [Student]() //教师数组 var teachers = [Teacher]() //定义表头数组 var tableTitle = ["Teacher", "Student"] //定义一个表视图 var table: UITableView! //右边按钮 var rightItem: UIBarButtonItem! //弹出框 var alert: UIAlertController! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. self.title = "table" self.view.backgroundColor = UIColor.white //生成3个Teacher对象 for i in 1...3 { let temp = Teacher(title: "教授", firstName: "张", lastName: "\(i)", age: 21, gender: .female, department: .one) teachers.append(temp) } //生成4个Student对象 for i in 1..<5 { let temp = Student(stuNo: 2015110100 + i, firstName: "李", lastName: "\(i)", age: 19, gender: .male, department: .two) students.append(temp) } //按全名排序 teachers.sort { return $0.fullName < $1.fullName } students.sort { return $0.fullName < $1.fullName } //创建表视图,并设置代理和数据源 table = UITableView(frame: self.view.bounds) table.delegate = self table.dataSource = self self.view.addSubview(table) //导航栏控制器右边的按钮 rightItem = UIBarButtonItem(title: "编辑", style: .plain, target: self, action: #selector(edit)) self.navigationItem.rightBarButtonItem = rightItem //导航栏控制器左边的按钮 let leftItem = UIBarButtonItem(title: "添加", style: .plain, target: self, action: #selector(addStudent)) self.navigationItem.leftBarButtonItem = leftItem } /// 添加学生提示框 @objc func addStudent() { alert = UIAlertController(title: "hh", message: "ss", preferredStyle: .alert) alert.addTextField { (textField) in textField.placeholder = "学生学号" } alert.addTextField { (textField) in textField.placeholder = "学生姓" } alert.addTextField { (textField) in textField.placeholder = "学生名" } alert.addTextField { (textField) in textField.placeholder = "学生性别" } alert.addTextField { (textField) in textField.placeholder = "学生年龄" } let OKBtn = UIAlertAction(title: "确定", style: .default) { (alert) in self.add() } let cancelBtn = UIAlertAction(title: "取消", style: .cancel, handler: nil) alert.addAction(OKBtn) alert.addAction(cancelBtn) self.present(alert, animated: true, completion: nil) } /// 添加学生 func add() { let no = Int(alert.textFields![0].text!) let firstName = alert.textFields![1].text! let lastName = alert.textFields![2].text! let gender: Gender switch alert.textFields![3].text! { case "男": gender = .male case "女": gender = .female default: gender = .unknow } let age = Int(alert.textFields![4].text!) let student = Student(stuNo: no!, firstName: firstName, lastName: lastName, age: age!, gender: gender) students.append(student) table.reloadData() } /// 编辑表视图 @objc func edit() { if table.isEditing { rightItem.title = "编辑" table.isEditing = false } else { rightItem.title = "完成" table.isEditing = true } } // MARK: delegate func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle { return .delete } func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? { return "删除" } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let category = tableTitle[indexPath.section] let name: String if indexPath.section == 0 { name = teachers[indexPath.row].fullName } else { name = students[indexPath.row].fullName } let message = "you selected \(category), name: \(name)" let alert = UIAlertController(title: "系统提示", message: message, preferredStyle: .alert) let OKBtn = UIAlertAction(title: "确定", style: .default, handler: nil) alert.addAction(OKBtn) self.present(alert, animated: true, completion: nil) } // MARK: data source func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == UITableViewCellEditingStyle.delete { if indexPath.section == 0 { teachers.remove(at: indexPath.row) } else { students.remove(at: indexPath.row) } tableView.deleteRows(at: [indexPath], with: .left) } } func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { if sourceIndexPath.section != destinationIndexPath.section { tableView.reloadData() } else { if sourceIndexPath.section == 0 { teachers.insert(teachers.remove(at: sourceIndexPath.row), at: destinationIndexPath.row) } else { students.insert(students.remove(at: sourceIndexPath.row), at: destinationIndexPath.row) } } } func numberOfSections(in tableView: UITableView) -> Int { return tableTitle.count } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return tableTitle[section] } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if section == 0 { return teachers.count } else { return students.count } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let identifier = tableTitle[indexPath.section] var cell = tableView.dequeueReusableCell(withIdentifier: identifier) if cell == nil { let style: UITableViewCellStyle = (identifier == "Teacher") ? .subtitle : .default cell = UITableViewCell(style: style, reuseIdentifier: identifier) cell?.accessoryType = .disclosureIndicator } switch identifier { case "Teacher": cell?.textLabel?.text = teachers[indexPath.row].fullName cell?.detailTextLabel?.text = teachers[indexPath.row].title case "Student": cell?.textLabel?.text = students[indexPath.row].fullName default: break } return cell! } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. }}
- (作业)TableView、Delegate、DataSource
- tableview中的delegate、DataSource原理
- iOS tableview的delegate和datasource执行顺序
- TableView dataSource
- UICollectionView: DataSource and Delegate
- Datasource 与 Delegate的对比
- UITableView中的DataSource和delegate
- tableview delegate 详解
- Core Plot中DataSource和Delegate
- UITableView的数据源(dataSource)和代理(delegate)
- 分离 UITableView 的 Delegate 和 Datasource
- 分离tableview的datasource , 实现ViewController “瘦身”
- iOS DataSource从tableview分离 简化viewController
- 学习笔记-UITableView的数据源(dataSource)和代理(delegate)
- 学习笔记-UITableView的数据源(dataSource)和代理(delegate)
- 打造轻量级 tableViewController 之抽离 DataSource/Delegate
- UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath
- UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath
- bootstrap-table源码解读
- 动态规划入门到熟练之路-----0
- Spring源码分析----AOP概念(Advice,Pointcut,Advisor)和AOP的设计与实现
- Web性能提升
- 二叉树-3
- (作业)TableView、Delegate、DataSource
- python将py文件转换为pyc
- iOS中的生命周期总结
- CTX学长的快速幂
- linux基础(十二)虚拟机上外网
- jquery 1.7.2的 live/on事件 $.ajax的 async:false,
- 每天回顾linux命令(stat)
- Jenkins自动部署Maven +tomcat+linux环境java项目并自动部署到外网(补)
- linux软链接的创建、删除和更新