项目概括
制作一个仿任务清单的App,主要为了练习TableViewController。
TableViewController的基本使用
- row, indexPath
//row -> indexPath
let indexPath = IndexPath(row: row, section: 0)
//indexPath -> row
let row = indexPath.row
- indexPath, cell
//indexPath -> cell
let cell = tableView.cellForRow(at: indexPath)
//cell -> indexPath
let indexPath = tableView.indexPath(for: cell)
- row, cell
//row -> cell
let indexPath = IndexPath(row: row, section: section)
let cell = ableView.cellForRow(at: IndexPath)
//cell -> row
let row = tableView.indexPath(for: cell)!.row
Todos cell 设置
- 新建一个类继承UITableViewCell,链接类和对象,设置cell的identifier
class TodoCell: UITableViewCell {
@IBOutlet weak var checkMark: UILabel!
@IBOutlet weak var todo: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
table view data source设置
- numberOfSection中设置区域数量
- numberOfRowsInSection中设置一个区域中的段落的数量
- cellForRowAt 中对cell进行配置,注意要将cell强制转换类型
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return todos.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "todo", for: indexPath) as! TodoCell
// Configure the cell...
cell.checkMark.text = todos[indexPath.row].checked ? "√" : ""
cell.todo.text = todos[indexPath.row].name
return cell
}
设置点击一行后的事件
- didSelectRowAt的用处是点击一行后的事件
- cellForRow是tableView实例下的方法,根据indexPath返回相应的cell
- deselectRow是tableView实例下的方法,根据indexPath取消cell的选中状态
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
todos[indexPath.row].checked = !todos[indexPath.row].checked
let cell = tableView.cellForRow(at: indexPath) as! TodoCell
cell.checkMark.text = todos[indexPath.row].checked ? "√" : ""
tableView.deselectRow(at: indexPath, animated: false)
}
添加Navigation Controller
- 将Todos页面添加到Navigation Controller
- 添加一个新的TableViewController页面,与第一个页面通过show方式连接
- 在Navigation Bar中勾选prefer large titles使标题变大
- 在Add Todo页面中添加Navigation Item作为标题
反向传值,从第二个页面传值到第一个页面
- 从add todo页面把输入的数据传递到todos页面:
- 定义协议,协议中只有一个方法addTodo
- 在add todo页面实例化协议,并执行addTodo方法
- 在todos页面继承协议,实现addTodo方法,在prepare方法中将协议委托给todos页面的控制器
protocol TodoDelegate {
func didAdd(name: String) -> Void
}
@IBAction func done(_ sender: Any) {
if let name = todoInput.text, !name.isEmpty {
delegate?.didAdd(name: name)
}
navigationController?.popViewController(animated: true)
}
extension TodosController: TodoDelegate {
func didAdd(name: String) {
todos.append(Todo(name: name, checked: false))
let indexPath = IndexPath(row: todos.count - 1, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
if segue.identifier == "addTodo" {
let vc = segue.destination as! TodoController
vc.delegate = self
}
}
更新todo
- control拖拽选择Accessory Action Show
- 先正向传值,把当前todo的信息传递到todo页面
- sender指通过哪个对象进行segue,这里通过点击cell进行segue,那么sender就是被点击的cell,通过类型转换获取被点击的cell
- 通过cell找到indexPath
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
let vc = segue.destination as! TodoController
vc.delegate = self
if segue.identifier == "editTodo" {
let cell = sender as! TodoCell
//通过cell获取indexPath
row = tableView.indexPath(for: cell)!.row
vc.name = todos[row].name
}
}
- 修改todo页面的标题,改为edit,navigationItem是隐藏的成员
navigationItem.title = "edit"
- 增加协议里的方法,点击提交按钮后执行委托
protocol TodoDelegate {
func didAdd(name: String) -> Void
func didEdit(name: String) -> Void
}
@IBAction func done(_ sender: Any) {
if let name = todoInput.text, !name.isEmpty {
if self.name != nil {
delegate?.didEdit(name: name)
} else {
delegate?.didAdd(name: name)
}
}
navigationController?.popViewController(animated: true)
}
- 实现委托
func didEdit(name: String) {
todos[row].name = name
let indexPath = IndexPath(row: row, section: 0)
let cell = tableView.cellForRow(at: indexPath) as! TodoCell
cell.todo.text = name
}
左滑删除
- 设置leftBarButtonItem
navigationItem.leftBarButtonItem = editButtonItem
- 设置table view的Editing属性为Multiple Selection During Editing
- 重载setEditing方法
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
editButtonItem.title = isEditing ? "Finish" : "Edit"
}
- 修改didSelectRowAt,只有当处于非编辑状态的时候,点击cell才会调用didSelectRowAt方法
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if !isEditing {
todos[indexPath.row].checked = !todos[indexPath.row].checked
let cell = tableView.cellForRow(at: indexPath) as! TodoCell
cell.checkMark.text = todos[indexPath.row].checked ? "√" : ""
tableView.deselectRow(at: indexPath, animated: false)
}
}
- 重载editingStyle方法,和titleForDeleteConfirmationForRowAt方法
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
todos.remove(at: indexPath.row)
// Delete the row from the data source
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
override func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? {
return "delete"
}
批量删除
- tableView.indexPathsForSelectedRows返回[indexPath],表示多选被选中的cell
- 数组的remove方法
- tableView的deleteRows方法,删除多个cell
- 把更新视图的语句放在beginUpdates和endUpdates中间可以优化运行,tableView.reloadData方法也可以优化运行,但视图修改没有动画效果
@IBAction func batchDelete(_ sender: Any) {
let indexPaths = tableView.indexPathsForSelectedRows
if let indexPaths = indexPaths {
for indexPath in indexPaths {
todos.remove(at: indexPath.row)
}
tableView.beginUpdates()
tableView.deleteRows(at: indexPaths, with: .automatic)
tableView.endUpdates()
// tableView.reloadData()//没有动画效果
}
}
移动数据
- 重载moveRowAt方法
// Override to support rearranging the table view.
override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
//move data
let todo = todos.remove(at: fromIndexPath.row)
todos.insert(todo, at: to.row)
//update view
tableView.moveRow(at: fromIndexPath, to: to)
tableView.reloadData()
}
本地存储UserDefaults
- UserDefaults只支持Swift基础类型和Data类型,所以要把[Todo]类型的数据存储起来需要先编码程Data类型,然后再存储
func saveData() -> Void {
do {
let data = try JSONEncoder().encode(todos)
UserDefaults.standard.set(data, forKey: "todos")
} catch {
print(error)
}
}
- 解码过程类似
if let data = UserDefaults.standard.data(forKey: "todos"){
do{
todos = try JSONDecoder().decode([Todo].self, from: data)
}catch {
print(error)
}
}