(作业)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.    }}
原创粉丝点击