Swift IOS Project Todos App

    技术2023-09-04  116

    项目概括

    制作一个仿任务清单的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返回相应的celldeselectRow是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) } }
    Processed: 0.014, SQL: 9