iOS开发Swift-15-沙盒sandbox,JSON编码和Codable协议,本地数据存储,SQLite增删改查,视图按照数据排序-待办事项App进阶版...

2023-11-15

1.在待办事项App中,寻找沙盒路径.

 TodosTableVC-Delegate:

import UIKit

//UITableViewDelegate
extension TodosTableVC{
    //当用户点击cell的时候调用
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true) //取消选择cell以让底色迅速消失
        
//        找到storyboard上的vc
//        let vc = storyboard?.instantiateViewController(withIdentifier: kTodoTableVCID) as! TodoTableVC
//        用代码进行push跳转
//        navigationController?.pushViewController(vc, animated: true)
    }
    
    //自定义左滑删除按钮的文本
//    override func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? {
//        return "点击以删除"
//    }
    
    //编辑状态下cell左边的按钮-设置为none;因仍旧需要左滑删除功能,故在非编辑状态下仍需返回.delete
    override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        isEditing ? .none : .delete
    }
    //取消编辑状态下cell的缩进
    override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { false }
}


//TodoTableVCDelegate
//反向传值
extension TodosTableVC: TodoTableVCDelegate{
    //增删改查-增
    func didAdd(name: String) {
        //添加数据
        todos.append(Todo(name: name, checked: false))
        UserDefaults.standard.set(todos,forKey: "todos")
        //根据最新数据更新视图
        tableView.insertRows(at: [IndexPath(row: todos.count - 1, section: 0)], with: .automatic)
    }
    
    //增删改查-改
    func didEdit(name: String) {
        //改数据
        todos[row].name = name
        
        //根据最新数据更新视图
        
        //1.通过indexPath找cell,继而找到里面的todoLabel,然后改text
//        let indexPath = IndexPath(row: row, section: 0)
//        let cell = tableView.cellForRow(at: indexPath) as! TodoCell
//        cell.todoLabel.text = todos[row].name
        
        //2.刷新整个tableView(不用过度担心耗资源问题)
        tableView.reloadData()
        
    }
}

 TodosTableVC:

import UIKit

//present和dismiss
//push和pop(压栈/入栈和出栈)

class TodosTableVC: UITableViewController {
    
    var todos = [
        Todo(name: "学习Lebus的《iOS基础版》课程", checked: false),
        Todo(name: "学习Lebus的《iOS进阶版》课程", checked: true),
        Todo(name: "学习Lebus的《iOS仿小红书实战项目》课程", checked: false),
        Todo(name: "学习Lebus的《iOS推送》课程", checked: false),
        Todo(name: "学习Lebus的《iOS-SwiftUI》课程", checked: false)
    ]


    var row = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        //print(todos)
        //isEditing = true //可手动修改isEditing

        //Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        //左边的排序按钮-用于对待办事项重新排序
        editButtonItem.title = nil
        editButtonItem.image = pointIcon("arrow.up.arrow.down.circle.fill")
        navigationItem.leftBarButtonItem = editButtonItem
        
        //右边的加号按钮-用于添加待办事项
        navigationItem.rightBarButtonItem?.image = pointIcon("plus.circle.fill")
        
        //沙盒sandbox
        print(NSHomeDirectory())
    }
    
    //点下editButtonItem后调用
    override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        
        if isEditing{
            editButtonItem.image = nil
            editButtonItem.title = "完成"
        }else{
            editButtonItem.title = nil
            editButtonItem.image = pointIcon("arrow.up.arrow.down.circle.fill")
        }
    }


    // MARK: - Navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let vc = segue.destination as! TodoTableVC
        vc.delegate = self
        
        if segue.identifier == kEditTodoID{
            let cell = sender as! TodoCell //sender就是用户点击的那个cell
            
            //1.cell-->indexPath
            row = tableView.indexPath(for: cell)!.row
            //2.indexPath-->cell(下述)
            //tableView.cellForRow(at: indexPath) as! TodoCell
            
            vc.name = todos[row].name //正向传值
        }
    }
    

}

启动运行:

 找到在模拟器上的沙盒路径:/Users/linyi/Library/Developer/CoreSimulator/Devices/10416348-B6C4-4F06-B07F-06F4FA9346FB/data/Containers/Data/Application/6580A342-D8E6-4A38-9F27-10E1FC890354

找到沙盒文件夹:

 通过观察文件夹可知,能够放入沙盒的数据类型是有限制的,所以需要使用JSON编码和Codable协议将数据类型转换为Data类型.

2.JSON编码和Codable协议

Todo:

import Foundation

//class Todo{
//    var name = ""
//    var checked = false
//}

//结构体-值类型
struct Todo: Codable {  //遵循编码协议和解码协议
    var name: String
    var checked: Bool
//    init(name: String, checked: Bool){
//        self.name = name
//        self.checked = checked
//    }
}

Constants:

import UIKit

let kTodoTableVCID = "TodoTableVCID"

let kTodoCellID = "TodoCellID"
let kAddTodoID = "AddTodoID"
let kEditTodoID = "EditTodoID"

let kTodosKey = "TodosKey"

func pointIcon(_ iconName: String, _ pointSize: CGFloat = 22) -> UIImage?{
    let config = UIImage.SymbolConfiguration(pointSize: pointSize)
    return UIImage(systemName: iconName, withConfiguration: config)
}

TodosTableVC-Delegate:

import UIKit

//UITableViewDelegate
extension TodosTableVC{
    //当用户点击cell的时候调用
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true) //取消选择cell以让底色迅速消失
        
//        找到storyboard上的vc
//        let vc = storyboard?.instantiateViewController(withIdentifier: kTodoTableVCID) as! TodoTableVC
//        用代码进行push跳转
//        navigationController?.pushViewController(vc, animated: true)
    }
    
    //自定义左滑删除按钮的文本
//    override func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? {
//        return "点击以删除"
//    }
    
    //编辑状态下cell左边的按钮-设置为none;因仍旧需要左滑删除功能,故在非编辑状态下仍需返回.delete
    override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        isEditing ? .none : .delete
    }
    //取消编辑状态下cell的缩进
    override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { false }
}


//TodoTableVCDelegate
//反向传值
extension TodosTableVC: TodoTableVCDelegate{
    //增删改查-增
    func didAdd(name: String) {
        //添加数据
        todos.append(Todo(name: name, checked: false))
        //编码
        do {
            let data = try JSONEncoder().encode(todos)  //todos类型转换为data
            UserDefaults.standard.set(data,forKey: kTodosKey)   //data存入沙盒
        }catch {
            print("编码错误:", error)
        }
        
        //根据最新数据更新视图
        tableView.insertRows(at: [IndexPath(row: todos.count - 1, section: 0)], with: .automatic)
    }
    
    //增删改查-改
    func didEdit(name: String) {
        //改数据
        todos[row].name = name
        
        //根据最新数据更新视图
        
        //1.通过indexPath找cell,继而找到里面的todoLabel,然后改text
//        let indexPath = IndexPath(row: row, section: 0)
//        let cell = tableView.cellForRow(at: indexPath) as! TodoCell
//        cell.todoLabel.text = todos[row].name
        
        //2.刷新整个tableView(不用过度担心耗资源问题)
        tableView.reloadData()
        
    }
}

启动测试:

 发现数据已被存储到了本机.

3.在增删改的时候都使用本地存储.

TodosTableVC-DataSource:

import UIKit

//UITableViewDataSource
extension TodosTableVC{

    //配置section的数量(不实现的话就相当于返回1)
    override func numberOfSections(in tableView: UITableView) -> Int { 1 }
    //配置row的数量
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { todos.count }
    //配置每个row里面显示什么内容
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: kTodoCellID, for: indexPath) as! TodoCell
        
        //增删改查-查
        let checkBoxBtn = cell.checkBoxBtn!
        let todoLabel = cell.todoLabel!
        let initSelected = todos[indexPath.row].checked
//        系统自带布局(虽然storyboard里面没有相应的UI控件,但仍旧可以这样使用)
//        var contentConfiguration = cell.defaultContentConfiguration()
//        contentConfiguration.text = "昵称"
//        contentConfiguration.secondaryText = "个性签名"
//        contentConfiguration.image = UIImage(systemName: "star")
//        cell.contentConfiguration = contentConfiguration
        
        //配置初始数据
        checkBoxBtn.isSelected = initSelected
        todoLabel.text = todos[indexPath.row].name
        todoLabel.textColor = initSelected ? .tertiaryLabel : .label //三元运算符.等同于下面
//        if todos[indexPath.row].checked{
//            cell.todoLabel.textColor = .tertiaryLabel
//        }else{
//            cell.todoLabel.textColor = .label
//        }
        
        
        //增删改查-改
        //设置每个checkBoxBtn的tag值为当前的row,用于传值
        checkBoxBtn.tag = indexPath.row
        //添加点击事件,用户点击checkBoxBtn后会触发self下面的toggleCheck函数,并且系统会自动把本身(toggleCheck)作为参数传过去
        checkBoxBtn.addTarget(self, action: #selector(toggleCheck), for: .touchUpInside)
        
        return cell
    }

    
    //增删改查-删
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        //左滑删除
        if editingStyle == .delete {
            //删数据
            todos.remove(at: indexPath.row)
            saveData()  //本地化存储
            //根据最新数据更新视图
            tableView.reloadData()
        } 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, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
//        Swift里数组元素的移动(重新排序)
//        var arr = [1,2,3,4]
//        arr.remove(at: 0) //arr --> [2,3,4]
//        arr.insert(1, at: 3) //arr --> [2,3,4,1]
//        print(arr)
        
        //改数据
        let todoToRemove = todos[fromIndexPath.row] //一定要在删除数组某个元素前取出,不然后面取会取不到
        todos.remove(at: fromIndexPath.row)
        todos.insert(todoToRemove, at: to.row)
        //print(todos)
        saveData()   //本地化存储
        
        //系统自动更新视图(纯粹更新,不会调用DataSource)
        //但我们仍需刷新DataSource函数,因为要刷新indexPath,从而让用户点击CheckBox时数据能够准确
        tableView.reloadData()
    }
}

extension TodosTableVC{
    func saveData(){
        //编码
        do {
            let data = try JSONEncoder().encode(todos)  //todos类型转换为data
            UserDefaults.standard.set(data,forKey: kTodosKey)   //data存入沙盒
        }catch {
            print("编码错误:", error)
        }
    }
}

//监听函数
extension TodosTableVC{
    //用户点击checkBoxBtn触发
    @objc func toggleCheck(checkBoxBtn: UIButton){
        let row = checkBoxBtn.tag
        //修改数据
        todos[row].checked.toggle()
        //print("点击了第\(row + 1)行,todos变成了:",todos)
        saveData()    //本地化存储
        //根据最新数据更新视图
        let checked = todos[row].checked
        checkBoxBtn.isSelected = checked
        let cell = tableView.cellForRow(at: IndexPath(row: row, section: 0)) as! TodoCell
        cell.todoLabel.textColor = checked ? .tertiaryLabel : .label
    }
}

 TodosTableVC-Delegate:

import UIKit

//UITableViewDelegate
extension TodosTableVC{
    //当用户点击cell的时候调用
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true) //取消选择cell以让底色迅速消失
        
//        找到storyboard上的vc
//        let vc = storyboard?.instantiateViewController(withIdentifier: kTodoTableVCID) as! TodoTableVC
//        用代码进行push跳转
//        navigationController?.pushViewController(vc, animated: true)
    }
    
    //自定义左滑删除按钮的文本
//    override func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? {
//        return "点击以删除"
//    }
    
    //编辑状态下cell左边的按钮-设置为none;因仍旧需要左滑删除功能,故在非编辑状态下仍需返回.delete
    override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        isEditing ? .none : .delete
    }
    //取消编辑状态下cell的缩进
    override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { false }
}


//TodoTableVCDelegate
//反向传值
extension TodosTableVC: TodoTableVCDelegate{
    //增删改查-增
    func didAdd(name: String) {
        //添加数据
        todos.append(Todo(name: name, checked: false))
        saveData()   //本地化存储
        
        //根据最新数据更新视图
        tableView.insertRows(at: [IndexPath(row: todos.count - 1, section: 0)], with: .automatic)
    }
    
    //增删改查-改
    func didEdit(name: String) {
        //改数据
        todos[row].name = name
        saveData()   //本地化存储
        
        //根据最新数据更新视图
        
        //1.通过indexPath找cell,继而找到里面的todoLabel,然后改text
//        let indexPath = IndexPath(row: row, section: 0)
//        let cell = tableView.cellForRow(at: indexPath) as! TodoCell
//        cell.todoLabel.text = todos[row].name
        
        //2.刷新整个tableView(不用过度担心耗资源问题)
        tableView.reloadData()
        
    }
}

4.在查询数据的时候使用解码从本地数据中查询待办事项.

TodosTableVC:

import UIKit

//present和dismiss
//push和pop(压栈/入栈和出栈)

class TodosTableVC: UITableViewController {
    
    var todos: [Todo] = [
//        Todo(name: "学习Lebus的《iOS基础版》课程", checked: false),
//        Todo(name: "学习Lebus的《iOS进阶版》课程", checked: true),
//        Todo(name: "学习Lebus的《iOS仿小红书实战项目》课程", checked: false),
//        Todo(name: "学习Lebus的《iOS推送》课程", checked: false),
//        Todo(name: "学习Lebus的《iOS-SwiftUI》课程", checked: false)
    ]


    var row = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        //print(todos)
        //isEditing = true //可手动修改isEditing

        //Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        //左边的排序按钮-用于对待办事项重新排序
        editButtonItem.title = nil
        editButtonItem.image = pointIcon("arrow.up.arrow.down.circle.fill")
        navigationItem.leftBarButtonItem = editButtonItem
        
        //右边的加号按钮-用于添加待办事项
        navigationItem.rightBarButtonItem?.image = pointIcon("plus.circle.fill")
        
        //沙盒sandbox
        print(NSHomeDirectory())
        
        //根据key在沙盒中取data数据
        if let data = UserDefaults.standard.data(forKey: kTodosKey){
            //解码
            if let todos = try? JSONDecoder().decode([Todo].self, from: data){
                //想把数据从data类型解码成数组里边元素是Todo的类型
                self.todos = todos
            }else{
                print("解码失败")
            }
        }
        
    }
    
    //点下editButtonItem后调用
    override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        
        if isEditing{
            editButtonItem.image = nil
            editButtonItem.title = "完成"
        }else{
            editButtonItem.title = nil
            editButtonItem.image = pointIcon("arrow.up.arrow.down.circle.fill")
        }
    }


    // MARK: - Navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let vc = segue.destination as! TodoTableVC
        vc.delegate = self
        
        if segue.identifier == kEditTodoID{
            let cell = sender as! TodoCell //sender就是用户点击的那个cell
            
            //1.cell-->indexPath
            row = tableView.indexPath(for: cell)!.row
            //2.indexPath-->cell(下述)
            //tableView.cellForRow(at: indexPath) as! TodoCell
            
            vc.name = todos[row].name //正向传值
        }
    }
    

}

 启动测试:

 关闭应用,再次打开:

 5.每次都直接覆盖存储,数据多了可能会影响系统性能.所以使用Core Data来提高效率.

以前在新建项目的时候勾选了Core Data,所以系统为我们自动生成了DataModel.

配置Core Data:

 这时Todo这个类就暂时不需要了.将它注释掉.

Todo:

import Foundation

//class Todo{
//    var name = ""
//    var checked = false
//}

//结构体-值类型
//struct Todo: Codable {
//    var name: String
//    var checked: Bool
////    init(name: String, checked: Bool){
////        self.name = name
////        self.checked = checked
////    }
//}

6.Todos的数据库添加操作.

将数据库中的name和checked都取消可选类型,设置默认值.

 新建一个用于存放存储代码的swift文件.

修改新增部分代码.

TodosTableVC-Delegate:

import UIKit

//UITableViewDelegate
extension TodosTableVC{
    //当用户点击cell的时候调用
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true) //取消选择cell以让底色迅速消失
        
//        找到storyboard上的vc
//        let vc = storyboard?.instantiateViewController(withIdentifier: kTodoTableVCID) as! TodoTableVC
//        用代码进行push跳转
//        navigationController?.pushViewController(vc, animated: true)
    }
    
    //自定义左滑删除按钮的文本
//    override func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? {
//        return "点击以删除"
//    }
    
    //编辑状态下cell左边的按钮-设置为none;因仍旧需要左滑删除功能,故在非编辑状态下仍需返回.delete
    override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        isEditing ? .none : .delete
    }
    //取消编辑状态下cell的缩进
    override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { false }
}


//TodoTableVCDelegate
//反向传值
extension TodosTableVC: TodoTableVCDelegate{
    //增删改查-增
    func didAdd(name: String) {
        //添加数据
//        todos.append(Todo(name: name, checked: false))
//        saveData()   //本地化存储
        //修改内存中的数据,再将修改后的数据存到本地.
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        let context = appDelegate.persistentContainer.viewContext
        let todo = Todo(context: context)
        todo.name = name
        todos.append(todo)
        appDelegate.saveContext()
        
        //根据最新数据更新视图
        tableView.insertRows(at: [IndexPath(row: todos.count - 1, section: 0)], with: .automatic)
    }
    
    //增删改查-改
    func didEdit(name: String) {
        //改数据
        todos[row].name = name
        //saveData()   //本地化存储
        
        //根据最新数据更新视图
        
        //1.通过indexPath找cell,继而找到里面的todoLabel,然后改text
//        let indexPath = IndexPath(row: row, section: 0)
//        let cell = tableView.cellForRow(at: indexPath) as! TodoCell
//        cell.todoLabel.text = todos[row].name
        
        //2.刷新整个tableView(不用过度担心耗资源问题)
        tableView.reloadData()
        
    }
}

通过地址/Users/linyi/Library/Developer/CoreSimulator/Devices/10416348-B6C4-4F06-B07F-06F4FA9346FB/data/Containers/Data/Application/0B19CC42-A036-492E-9F77-AF56F9EF4071/Library/ApplicationSupport的Todos.sqlite查看数据库:

 7.删除待办事项后更新本地数据库.

TodosTableVC-DataSource:

import UIKit

//UITableViewDataSource
extension TodosTableVC{

    //配置section的数量(不实现的话就相当于返回1)
    override func numberOfSections(in tableView: UITableView) -> Int { 1 }
    //配置row的数量
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { todos.count }
    //配置每个row里面显示什么内容
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: kTodoCellID, for: indexPath) as! TodoCell
        
        //增删改查-查
        let checkBoxBtn = cell.checkBoxBtn!
        let todoLabel = cell.todoLabel!
        let initSelected = todos[indexPath.row].checked
//        系统自带布局(虽然storyboard里面没有相应的UI控件,但仍旧可以这样使用)
//        var contentConfiguration = cell.defaultContentConfiguration()
//        contentConfiguration.text = "昵称"
//        contentConfiguration.secondaryText = "个性签名"
//        contentConfiguration.image = UIImage(systemName: "star")
//        cell.contentConfiguration = contentConfiguration
        
        //配置初始数据
        checkBoxBtn.isSelected = initSelected
        todoLabel.text = todos[indexPath.row].name
        todoLabel.textColor = initSelected ? .tertiaryLabel : .label //三元运算符.等同于下面
//        if todos[indexPath.row].checked{
//            cell.todoLabel.textColor = .tertiaryLabel
//        }else{
//            cell.todoLabel.textColor = .label
//        }
        
        
        //增删改查-改
        //设置每个checkBoxBtn的tag值为当前的row,用于传值
        checkBoxBtn.tag = indexPath.row
        //添加点击事件,用户点击checkBoxBtn后会触发self下面的toggleCheck函数,并且系统会自动把本身(toggleCheck)作为参数传过去
        checkBoxBtn.addTarget(self, action: #selector(toggleCheck), for: .touchUpInside)
        
        return cell
    }

    
    //增删改查-删
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        //左滑删除
        if editingStyle == .delete {
            //删除本地中的数据
            context.delete(todos[indexPath.row])
            //删内存中的数据
            todos.remove(at: indexPath.row)
            appDelegate.saveContext()
            //saveData()  //本地化存储
            //根据最新数据更新视图
            tableView.reloadData()
        } 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, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
//        Swift里数组元素的移动(重新排序)
//        var arr = [1,2,3,4]
//        arr.remove(at: 0) //arr --> [2,3,4]
//        arr.insert(1, at: 3) //arr --> [2,3,4,1]
//        print(arr)
        
        //改数据
        let todoToRemove = todos[fromIndexPath.row] //一定要在删除数组某个元素前取出,不然后面取会取不到
        todos.remove(at: fromIndexPath.row)
        todos.insert(todoToRemove, at: to.row)
        //print(todos)
        //saveData()   //本地化存储
        
        //系统自动更新视图(纯粹更新,不会调用DataSource)
        //但我们仍需刷新DataSource函数,因为要刷新indexPath,从而让用户点击CheckBox时数据能够准确
        tableView.reloadData()
    }
}



//监听函数
extension TodosTableVC{
    //用户点击checkBoxBtn触发
    @objc func toggleCheck(checkBoxBtn: UIButton){
        let row = checkBoxBtn.tag
        //修改数据
        todos[row].checked.toggle()
        //print("点击了第\(row + 1)行,todos变成了:",todos)
        //saveData()    //本地化存储
        //根据最新数据更新视图
        let checked = todos[row].checked
        checkBoxBtn.isSelected = checked
        let cell = tableView.cellForRow(at: IndexPath(row: row, section: 0)) as! TodoCell
        cell.todoLabel.textColor = checked ? .tertiaryLabel : .label
    }
}

 8.编辑待办事项后更新本地数据库.

TodosTableVC-Delegate:

import UIKit

//UITableViewDelegate
extension TodosTableVC{
    //当用户点击cell的时候调用
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true) //取消选择cell以让底色迅速消失
        
//        找到storyboard上的vc
//        let vc = storyboard?.instantiateViewController(withIdentifier: kTodoTableVCID) as! TodoTableVC
//        用代码进行push跳转
//        navigationController?.pushViewController(vc, animated: true)
    }
    
    //自定义左滑删除按钮的文本
//    override func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? {
//        return "点击以删除"
//    }
    
    //编辑状态下cell左边的按钮-设置为none;因仍旧需要左滑删除功能,故在非编辑状态下仍需返回.delete
    override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        isEditing ? .none : .delete
    }
    //取消编辑状态下cell的缩进
    override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { false }
}


//TodoTableVCDelegate
//反向传值
extension TodosTableVC: TodoTableVCDelegate{
    //增删改查-增
    func didAdd(name: String) {
        //添加数据
//        todos.append(Todo(name: name, checked: false))
//        saveData()   //本地化存储
        //修改内存中的数据,再将修改后的数据存到本地.
       
        let todo = Todo(context: context)
        todo.name = name
        todos.append(todo)
        appDelegate.saveContext()
        
        //根据最新数据更新视图
        tableView.insertRows(at: [IndexPath(row: todos.count - 1, section: 0)], with: .automatic)
    }
    
    //增删改查-改
    func didEdit(name: String) {
        //改数据
        todos[row].name = name
        appDelegate.saveContext()
        //saveData()   //本地化存储
        
        //根据最新数据更新视图
        
        //1.通过indexPath找cell,继而找到里面的todoLabel,然后改text
//        let indexPath = IndexPath(row: row, section: 0)
//        let cell = tableView.cellForRow(at: indexPath) as! TodoCell
//        cell.todoLabel.text = todos[row].name
        
        //2.刷新整个tableView(不用过度担心耗资源问题)
        tableView.reloadData()
        
    }
}

 TodosTableVC-DataSource:

import UIKit

//UITableViewDataSource
extension TodosTableVC{

    //配置section的数量(不实现的话就相当于返回1)
    override func numberOfSections(in tableView: UITableView) -> Int { 1 }
    //配置row的数量
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { todos.count }
    //配置每个row里面显示什么内容
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: kTodoCellID, for: indexPath) as! TodoCell
        
        //增删改查-查
        let checkBoxBtn = cell.checkBoxBtn!
        let todoLabel = cell.todoLabel!
        let initSelected = todos[indexPath.row].checked
//        系统自带布局(虽然storyboard里面没有相应的UI控件,但仍旧可以这样使用)
//        var contentConfiguration = cell.defaultContentConfiguration()
//        contentConfiguration.text = "昵称"
//        contentConfiguration.secondaryText = "个性签名"
//        contentConfiguration.image = UIImage(systemName: "star")
//        cell.contentConfiguration = contentConfiguration
        
        //配置初始数据
        checkBoxBtn.isSelected = initSelected
        todoLabel.text = todos[indexPath.row].name
        todoLabel.textColor = initSelected ? .tertiaryLabel : .label //三元运算符.等同于下面
//        if todos[indexPath.row].checked{
//            cell.todoLabel.textColor = .tertiaryLabel
//        }else{
//            cell.todoLabel.textColor = .label
//        }
        
        
        //增删改查-改
        //设置每个checkBoxBtn的tag值为当前的row,用于传值
        checkBoxBtn.tag = indexPath.row
        //添加点击事件,用户点击checkBoxBtn后会触发self下面的toggleCheck函数,并且系统会自动把本身(toggleCheck)作为参数传过去
        checkBoxBtn.addTarget(self, action: #selector(toggleCheck), for: .touchUpInside)
        
        return cell
    }

    
    //增删改查-删
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        //左滑删除
        if editingStyle == .delete {
            //删除本地中的数据
            context.delete(todos[indexPath.row])
            //删内存中的数据
            todos.remove(at: indexPath.row)
            appDelegate.saveContext()
            //saveData()  //本地化存储
            //根据最新数据更新视图
            tableView.reloadData()
        } 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, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
//        Swift里数组元素的移动(重新排序)
//        var arr = [1,2,3,4]
//        arr.remove(at: 0) //arr --> [2,3,4]
//        arr.insert(1, at: 3) //arr --> [2,3,4,1]
//        print(arr)
        
        //改数据
        let todoToRemove = todos[fromIndexPath.row] //一定要在删除数组某个元素前取出,不然后面取会取不到
        todos.remove(at: fromIndexPath.row)
        todos.insert(todoToRemove, at: to.row)
        //print(todos)
        //saveData()   //本地化存储
        
        //系统自动更新视图(纯粹更新,不会调用DataSource)
        //但我们仍需刷新DataSource函数,因为要刷新indexPath,从而让用户点击CheckBox时数据能够准确
        tableView.reloadData()
    }
}



//监听函数
extension TodosTableVC{
    //用户点击checkBoxBtn触发
    @objc func toggleCheck(checkBoxBtn: UIButton){
        let row = checkBoxBtn.tag
        //修改数据
        todos[row].checked.toggle()
        //print("点击了第\(row + 1)行,todos变成了:",todos)
        //saveData()    //本地化存储
        appDelegate.saveContext()
        //根据最新数据更新视图
        let checked = todos[row].checked
        checkBoxBtn.isSelected = checked
        let cell = tableView.cellForRow(at: IndexPath(row: row, section: 0)) as! TodoCell
        cell.todoLabel.textColor = checked ? .tertiaryLabel : .label
    }
}

 9.从本地取出数据.

TodosTableVC:

import UIKit

//present和dismiss
//push和pop(压栈/入栈和出栈)

class TodosTableVC: UITableViewController {
    
    var todos: [Todo] = [
//        Todo(name: "学习Lebus的《iOS基础版》课程", checked: false),
//        Todo(name: "学习Lebus的《iOS进阶版》课程", checked: true),
//        Todo(name: "学习Lebus的《iOS仿小红书实战项目》课程", checked: false),
//        Todo(name: "学习Lebus的《iOS推送》课程", checked: false),
//        Todo(name: "学习Lebus的《iOS-SwiftUI》课程", checked: false)
    ]


    var row = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        //print(todos)
        //isEditing = true //可手动修改isEditing

        //Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        //左边的排序按钮-用于对待办事项重新排序
        editButtonItem.title = nil
        editButtonItem.image = pointIcon("arrow.up.arrow.down.circle.fill")
        navigationItem.leftBarButtonItem = editButtonItem
        
        //右边的加号按钮-用于添加待办事项
        navigationItem.rightBarButtonItem?.image = pointIcon("plus.circle.fill")
        
        //沙盒sandbox
        print(NSHomeDirectory())
        
//        //根据key在沙盒中取data数据
//        if let data = UserDefaults.standard.data(forKey: kTodosKey){
//            //解码
//            if let todos = try? JSONDecoder().decode([Todo].self, from: data){
//                //想把数据从data类型解码成数组里边元素是Todo的类型
//                self.todos = todos
//            }else{
//                print("解码失败")
//            }
//        }
        if let todos = try? context.fetch(Todo.fetchRequest()){
            self.todos = todos
        }else{
            print("从SQLite里面取数值失败了")
        }
    }
    
    //点下editButtonItem后调用
    override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        
        if isEditing{
            editButtonItem.image = nil
            editButtonItem.title = "完成"
        }else{
            editButtonItem.title = nil
            editButtonItem.image = pointIcon("arrow.up.arrow.down.circle.fill")
        }
    }


    // MARK: - Navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let vc = segue.destination as! TodoTableVC
        vc.delegate = self
        
        if segue.identifier == kEditTodoID{
            let cell = sender as! TodoCell //sender就是用户点击的那个cell
            
            //1.cell-->indexPath
            row = tableView.indexPath(for: cell)!.row
            //2.indexPath-->cell(下述)
            //tableView.cellForRow(at: indexPath) as! TodoCell
            
            vc.name = todos[row].name //正向传值
        }
    }
    

}

 启动测试:

 10.移动待办事项:主键+Int16+添加属性时的自动迁移.

在Entity中追加一个属性-排序ID.

 TodosTableVC-DataSource:

import UIKit

//UITableViewDataSource
extension TodosTableVC{

    //配置section的数量(不实现的话就相当于返回1)
    override func numberOfSections(in tableView: UITableView) -> Int { 1 }
    //配置row的数量
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { todos.count }
    //配置每个row里面显示什么内容
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: kTodoCellID, for: indexPath) as! TodoCell
        
        todos[indexPath.row].orderID = Int16(indexPath.row)
     appDelegate.saveContext() //增删改查-查 let checkBoxBtn = cell.checkBoxBtn! let todoLabel = cell.todoLabel! let initSelected = todos[indexPath.row].checked // 系统自带布局(虽然storyboard里面没有相应的UI控件,但仍旧可以这样使用) // var contentConfiguration = cell.defaultContentConfiguration() // contentConfiguration.text = "昵称" // contentConfiguration.secondaryText = "个性签名" // contentConfiguration.image = UIImage(systemName: "star") // cell.contentConfiguration = contentConfiguration //配置初始数据 checkBoxBtn.isSelected = initSelected todoLabel.text = todos[indexPath.row].name todoLabel.textColor = initSelected ? .tertiaryLabel : .label //三元运算符.等同于下面 // if todos[indexPath.row].checked{ // cell.todoLabel.textColor = .tertiaryLabel // }else{ // cell.todoLabel.textColor = .label // } //增删改查-改 //设置每个checkBoxBtn的tag值为当前的row,用于传值 checkBoxBtn.tag = indexPath.row //添加点击事件,用户点击checkBoxBtn后会触发self下面的toggleCheck函数,并且系统会自动把本身(toggleCheck)作为参数传过去 checkBoxBtn.addTarget(self, action: #selector(toggleCheck), for: .touchUpInside) return cell } //增删改查-删 override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { //左滑删除 if editingStyle == .delete { //删除本地中的数据 context.delete(todos[indexPath.row]) //删内存中的数据 todos.remove(at: indexPath.row) appDelegate.saveContext() //saveData() //本地化存储 //根据最新数据更新视图 tableView.reloadData() } 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, moveRowAt fromIndexPath: IndexPath, to: IndexPath) { // Swift里数组元素的移动(重新排序) // var arr = [1,2,3,4] // arr.remove(at: 0) //arr --> [2,3,4] // arr.insert(1, at: 3) //arr --> [2,3,4,1] // print(arr) //改数据 let todoToRemove = todos[fromIndexPath.row] //一定要在删除数组某个元素前取出,不然后面取会取不到 todos.remove(at: fromIndexPath.row) todos.insert(todoToRemove, at: to.row) //print(todos) //saveData() //本地化存储 //系统自动更新视图(纯粹更新,不会调用DataSource) //但我们仍需刷新DataSource函数,因为要刷新indexPath,从而让用户点击CheckBox时数据能够准确 tableView.reloadData() } } //监听函数 extension TodosTableVC{ //用户点击checkBoxBtn触发 @objc func toggleCheck(checkBoxBtn: UIButton){ let row = checkBoxBtn.tag //修改数据 todos[row].checked.toggle() //print("点击了第\(row + 1)行,todos变成了:",todos) //saveData() //本地化存储 appDelegate.saveContext() //根据最新数据更新视图 let checked = todos[row].checked checkBoxBtn.isSelected = checked let cell = tableView.cellForRow(at: IndexPath(row: row, section: 0)) as! TodoCell cell.todoLabel.textColor = checked ? .tertiaryLabel : .label } }

 测试:

 11.首次进入App首页时按照orderID从小到大的顺序进行排序.

TodosTableVC:

import UIKit

//present和dismiss
//push和pop(压栈/入栈和出栈)

class TodosTableVC: UITableViewController {
    
    var todos: [Todo] = [
//        Todo(name: "学习Lebus的《iOS基础版》课程", checked: false),
//        Todo(name: "学习Lebus的《iOS进阶版》课程", checked: true),
//        Todo(name: "学习Lebus的《iOS仿小红书实战项目》课程", checked: false),
//        Todo(name: "学习Lebus的《iOS推送》课程", checked: false),
//        Todo(name: "学习Lebus的《iOS-SwiftUI》课程", checked: false)
    ]


    var row = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        //print(todos)
        //isEditing = true //可手动修改isEditing

        //Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        //左边的排序按钮-用于对待办事项重新排序
        editButtonItem.title = nil
        editButtonItem.image = pointIcon("arrow.up.arrow.down.circle.fill")
        navigationItem.leftBarButtonItem = editButtonItem
        
        //右边的加号按钮-用于添加待办事项
        navigationItem.rightBarButtonItem?.image = pointIcon("plus.circle.fill")
        
        //沙盒sandbox
        print(NSHomeDirectory())
        
//        //根据key在沙盒中取data数据
//        if let data = UserDefaults.standard.data(forKey: kTodosKey){
//            //解码
//            if let todos = try? JSONDecoder().decode([Todo].self, from: data){
//                //想把数据从data类型解码成数组里边元素是Todo的类型
//                self.todos = todos
//            }else{
//                print("解码失败")
//            }
//        }
        //指定排序规则
        let request = Todo.fetchRequest()
        //根据orderID的顺序进行升序排列.
        request.sortDescriptors = [NSSortDescriptor(key: "orderID", ascending: true)]
        
        if let todos = try? context.fetch(request){
            self.todos = todos
        }else{
            print("从SQLite里面取数值失败了")
        }
    }
    
    //点下editButtonItem后调用
    override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        
        if isEditing{
            editButtonItem.image = nil
            editButtonItem.title = "完成"
        }else{
            editButtonItem.title = nil
            editButtonItem.image = pointIcon("arrow.up.arrow.down.circle.fill")
        }
    }


    // MARK: - Navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let vc = segue.destination as! TodoTableVC
        vc.delegate = self
        
        if segue.identifier == kEditTodoID{
            let cell = sender as! TodoCell //sender就是用户点击的那个cell
            
            //1.cell-->indexPath
            row = tableView.indexPath(for: cell)!.row
            //2.indexPath-->cell(下述)
            //tableView.cellForRow(at: indexPath) as! TodoCell
            
            vc.name = todos[row].name //正向传值
        }
    }
    

}

 启动测试:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

iOS开发Swift-15-沙盒sandbox,JSON编码和Codable协议,本地数据存储,SQLite增删改查,视图按照数据排序-待办事项App进阶版... 的相关文章

随机推荐

  • C语言进阶:C陷阱与缺陷(读书笔记总)

    大家不要只收藏不关注呀 哪怕只是点个赞也可以呀 粉丝私信发邮箱 免费发你PDF 最近读了一本C语言书 C陷阱与缺陷 还不错 挺适合刚刚工作后的人 特此分享读书笔记 写代码时应注意这些问题 笔记已做精简 读完大概需要30min 如果读起来感觉
  • 广义线性模型(GLM)

    在线性回归中 y丨x N 2 在逻辑回归中 y丨x Bernoulli 这两个都是GLM中的特殊的cases 我们首先引入一个指数族 the exponential family 的概念 如果一个分布能写成下列形式 那么我们说这个分布属于指
  • Bert机器问答模型QA(阅读理解)

    Github参考代码 https github com edmondchensj ChineseQA with BERT https zhuanlan zhihu com p 333682032 数据集来源于DuReader Dataset
  • Unity基础3——Resources资源动态加载

    一 特殊文件夹 一 工程路径获取 注意 该方式 获取到的路径 一般情况下 只在 编辑模式下使用 我们不会在实际发布游戏后 还使用该路径 游戏发布过后 该路径就不存在了 print Application dataPath 二 Resourc
  • C++ vector find()使用? ( if!=vec.end())

    std vector find是C STL中的一个函数 它可以用来在std vector中查找给定的元素 如果找到了这个元素 它将返回一个迭代器指向该元素 否则将返回一个名为end 的迭代器 下面是一个使用find的示例代码 include
  • C++11 条件变量(condition_variable) 使用详解

    官网 一 总述 在C 11中 我们可以使用条件变量 condition variable 实现多个线程间的同步操作 当条件不满足时 相关线程被一直阻塞 直到某种条件出现 这些线程才会被唤醒 主要成员函数如下 二 具体函数 1 wait函数
  • 泰勒阵列天线综合与matlab,阵列天线综合之切比雪夫低副瓣阵列设计Matlab

    在 自适应天线与相控阵 这门课中 我了解到了关于理想低副瓣阵列设计的一些方法 其中切比雪夫等副瓣阵列设计方法是一种基础的方法 故将其设计流程写成maltab程序供以后学习使用 在此分享一下 此方法全称为道尔夫 切比雪夫综合法 简称为切比雪夫
  • 量化交易框架开发实践(二)

    我们通过分析代码可以看出 PyAlgoTrade分为六个组件 Strategies Feeds Brokers DataSeries Technicals Optimizer 从业务流上看也是比较容易理解的 Feed 数据源 gt Data
  • 【C++】常用math函数

    C语言提供了很多实用的数学函数 如果要使用先添加头文件
  • Python手撸机器学习系列(一):感知机 (附原始形式和对偶形式Python实现代码)

    感知机 1 感知机的定义 感知机是二分类的线性模型 是神经网络和SVM的基础 输入特征 x X x X x X 输出 y
  • Integer.valueOf()方法 java

    Integer valueOf 方法实现如下 public static Integer valueOf int i final int offset 128 if i gt 128 i lt 127 must cache return I
  • C++ 数据结构:DS单链表--合并

    题目描述 假定两个单链表是递增有序 定义并实现以下函数 完成两个单链表的合并 继续保持递增有序 int LL merge ListNode La ListNode Lb 输入 第1行先输入n表示有n个数据 接着输入n个数据 第2行先输入m表
  • STM32HAL库微秒延时(μs)

    STM32HAL库微秒延时 s 单片机 STM32F407ZET6 软件版本 STM32CubeMX 4 20 1 单片机固件包 STM32Cube FW F4 V1 15 0 本代码是我于2019年8月参加全国大学生电子设计竞赛前做赛前准
  • IPC-核间通讯

    1 IPC通讯是AUTOSAR体系结构中的核心组成部分 它使得不同的软件组件可以相互通信 协同工作 从而实现整车系统的功能 IPC可以理解为核间通讯 就是一个芯片有多个核 现在想让多核之间通信 达到下面几个目的 数据共享 不同的软件组件之间
  • mysqld: Can‘t read dir of ‘/etc/mysql/conf.d/‘ (Errcode: 13 - Permission denied)

    在安装docker mysql 5 7版本时 出现错误 mysqld Can t read dir of etc mysql conf d Errcode 13 Permission denied mysqld ERROR Fatal er
  • matlab方差分析

    一 单因素方差分析 1 各组数据长度相等 p anoval x x每一列为一个单独的样本值 返回的P是一个概率 p gt 0 05 故接受H0 即数据之间没有显著差异 0 01
  • C# new与malloc

    目录 C new与malloc C new与malloc的区别 C new关键字底层做的操作 C new与malloc new关键字 new关键字在C 中用于实例化对象 并为其分配内存 它是面向对象编程的基本操作之一 使用new关键字可以在
  • kafka报错:creating broker listeners from xxx unable to parse xxx:9092 to a broker endpoint

    1 美图 2 背景 kafka报错 creating broker listeners from xxx unable to parse xxx 9092 to a broker endpoint 具体报错内容如下 这个是你的地址域名写错了
  • sql monitor简介

    Sql monitor 简介 11g 之后的版本 oracle 提供了一种实时 sql 监控工具 即 sql monitor 默认情况下 当 sql 开启并行 或者 sql 的单词执行时间超过 5 秒钟 sql monitor 就会自动触发
  • iOS开发Swift-15-沙盒sandbox,JSON编码和Codable协议,本地数据存储,SQLite增删改查,视图按照数据排序-待办事项App进阶版...

    1 在待办事项App中 寻找沙盒路径 TodosTableVC Delegate import UIKit UITableViewDelegate extension TodosTableVC 当用户点击cell的时候调用 override