欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

程序员文章站 2024-03-23 09:10:52
...

文章目录

0. 本篇重点

  • CRUD (☆☆☆☆☆)
  • Bar Button Item
  • 各种转场
    • 利用 id 区别转场的类型(Edit / Add)
    • 压栈,弹栈
  • Json 对象的编码和解码
  • UISearchResultsUpdating 协议
  • SearchBar

1. 项目整体框架

1.1 新建一个 Single View App
1.2 在 Main.storyboard 中添加一个 Navigation Controller
1.3 更改根界面

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

效果如下:

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

1.4 给详细页面的 ViewController 更名(第三个界面,最右边)

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

1.5 为“列表”界面添加 UIViewTableController 并关联

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

1.6 设置 Cell 到“详细“页面的 Edit 转场

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

并设置辨识ID

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

1.7 更改列表名 Swift — UIKit 之(6)—— Model 的 CRUD(文件版)
1.8 在列表标题右边添加 Add 入口 Bar Button Item

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

1.9 更改 Bar Button Item 样式为 ”Add“

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

1.10 添加 Add 转场

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

并设置辨识ID

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

1.11 ”详细“页面添加一个 Button

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

1.12 为”列表“界面的控制器添加 prepareForUnwind 函数来执行转场回来的时候要执行的事情

当”详细“页面点击Save的时候,我们需要做三件事:

  • 保存 Model 信息
  • 返回到”列表“页面
  • 将保存的信息显示到”列表“页面

因为”列表“界面保存了很多条 Student 的信息,所以我们将保存操作交给”列表“页面

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

1.13 在”详细“页面选中绑定这个函数

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

1.14 为这个转场设置辨识ID

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

1.15 在 ”Save“ 按钮函数中指定这个函数

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

这样我们在从”详细“页面点击 ”Save“ 转场回”列表“页面的时候就会执行这个 prepareForUnWind 函数了。

2. 定义 Student Model

//
//  Student.swift
//  Review_08
//
//  Created by Hedon - on 2020/5/11.
//  Copyright © 2020 Hedon -. All rights reserved.
//

import Foundation

//要对Student进行序列化的话需要遵守NSObject,Codable这两个协议
class Student:NSObject,Codable{
    
    //Student 的 4个属性
    var name:String = ""
    var birthday:String = ""
    var score:String = ""
    var address:String = ""
    
    //实现Codable协议,设定转为 Json 文件时的”键名“,有设置的话在转为 JSon 的时候就会转
    private enum CodingKeys: String, CodingKey{
        case name = "person_name";   //重命名
        case birthday
        case score
        case address  //比如如果这里注释掉 address,则序列化的时候就不会序列化 address
    }
    
    override init() {
    }
    
    init(name: String, birthday: String, score: String, address: String){
        self.name = name
        self.birthday = birthday
        self.score = score
        self.address = address
    }
    
    override var description: String{
        return "name:\(name), birthday:\(birthday), score:\(score), address\(address)"
    }
    
}

3. 编写通用的 Json 存储文件类

//
//  JasonFileManager.swift
//  Review_08
//
//  Created by Hedon - on 2020/5/11.
//  Copyright © 2020 Hedon -. All rights reserved.
//

import Foundation

class JasonFileManager<T:NSObject&Codable>
{
    var Records:[T] = []
    var Url:URL
    
    //构造方法,需要指定文件名
    init(filename:String){
        let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        Url = path.appendingPathComponent(filename)
    }
    
    //保存
    func Save(){
        //声明一个 JSON 编码器
        let encoder = JSONEncoder()
        
        do{
            let encodeDate:Data? = try encoder.encode(Records)  //编码
            try encodeDate!.write(to: Url,options: .atomic)     //写到文件中
        }
        catch
        {
            print(error)
        }
    }
    
    //加载
    func Load(){
        do{
            if let encodeData = try?Data.init(contentsOf: Url){  //从文件中获取数据
                let decoder = JSONDecoder()   //声明一个Json解码器
                Records = try decoder.decode([Student].self, from: encodeData) as! [T]
            }
        }
        catch
        {
            print(error)
        }
    }
}

4. 在 MasterTableViewController 中使用上面两个类

4.1 定义处理 Student 类的 Json 处理器
    //定义 Json 控制器的对象
    let jasonClass = JasonFileManager<Student>(filename: "studentInfo")
4.2 在”列表“页面加载是时候读取数据
    override func viewDidLoad() {
        super.viewDidLoad()

        //读取数据到 Records[] 中
        jasonClass.Load()
    }
4.3 在点击”Save“按钮转场回来的时候进行保存数据
    //转场回来前要执行的函数
    @IBAction func prepareForUnwind(segue:UIStoryboardSegue){
        //写
        jasonClass.Save()
    }
4.4 指定”列表“界面显示多少行(这里我们没有分区)
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return jasonClass.Records.count
    }
4.5 设置列表中每个 Cell 的呈现方式
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "masterCell", for: indexPath)

        //获取特定的某条 Student 记录
        let stu = jasonClass.Records[indexPath.row]

        cell.textLabel?.text = stu.name
        cell.detailTextLabel?.text = stu.address
        
        return cell
    }

注意这里要设置 Cell 的辨识ID 和更改样式为 Subtitle

5. 添加和修改

5.1 设计”详细“页面

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

5.2 编写 DetailViewController.swift
  • 页面加载时利用 student 变量呈现Model

  • Save 保存时将 Model 数据存储到 student 变量中

//
//  ViewController.swift
//  Review_08
//
//  Created by Hedon - on 2020/5/11.
//  Copyright © 2020 Hedon -. All rights reserved.
//

import UIKit

class DetailViewController: UIViewController {

    var studnet:Student?    //定义一个变量,当界面加载的时候来接收Student
    var isAdd:Bool = true   //定义一个变量,判断是 Add 还是 Edit
    
    
    @IBOutlet weak var nameText: UITextField!
    @IBOutlet weak var birthText: UITextField!
    @IBOutlet weak var scoreText: UITextField!
    @IBOutlet weak var addressText: UITextField!
    
    
    //界面加载的时候要呈现Model
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //解包
        if let stu = self.studnet
        {
            //呈现Model
            nameText.text = stu.name
            birthText.text = stu.birthday
            scoreText.text = stu.score
            addressText.text = stu.address
        }
    }

    //保存操作
    @IBAction func saveBtnPressed(_ sender: UIButton) {
        
        if let stu = self.studnet{
            stu.name = nameText.text!
            stu.birthday = birthText.text!
            stu.score = scoreText.text!
            stu.address = addressText.text!
        }
        
        /**
         withIdentifier:指定是哪个转场
         sender:发送者,我们自己
         */
        performSegue(withIdentifier: "UnWind", sender: self)
    }
}

下面编写的是 MasterTableViewController.swift 文件

5.3 转场进入”详细“页面 —— 判断是 Edit 还是 Add
    //准备转场过去执行的函数
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        guard let detailViewController = segue.destination as? DetailViewController else{return}
        
        if segue.identifier == "Edit"   //修改
        {
            if let indexPath = tableView.indexPathForSelectedRow
            {
                detailViewController.studnet = jasonClass.Records[indexPath.row]        //如果是修改就从数据中读取当前选定的这一行所对应的的Model呈现出来
                detailViewController.isAdd = false
            }
        }
        else if segue.identifier == "Add"   //添加
        {
            
            detailViewController.studnet = Student()    //如果是添加则新实例化一个Student对象
            detailViewController.isAdd = true
        }
    }
5.4 从”详细“页面转场到”列表“页面 —— 把新 Add 的Student 添加到文件中
    //转场回来前要执行的函数
    @IBAction func prepareForUnwind(segue:UIStoryboardSegue){
        /**
         这里是源 source
         上面的prepare的目的地 destination
         */
        guard let detailViewController = segue.source as? DetailViewController else {return}
        
        //把新添加的Student添加到文件中
        if detailViewController.isAdd == true{
            jasonClass.Records.append(detailViewController.studnet!)
        }
        //写
        jasonClass.Save()
    }
5.5 刷新数据
    //转场过来后,当界面要展示的时候,我们需要重新加载数据
    override func viewWillAppear(_ animated: Bool) {
        tableView.reloadData()
    }
5.6 效果演示

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

6. 删除

6.1 开启 MasterTableViewController.swift 预置的关于 Edit 的两个函数

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

6.2 编写代码
    // Override to support conditional editing of the table view.
    override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        // Return false if you do not want the specified item to be editable.
        return true
    }
    

    
    // Override to support editing the table view.
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            jasonClass.Records.remove(at: indexPath.row)   //从文件中删除
            jasonClass.Save()                               //保存
            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
        }    
    }
6.3 效果演示

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

7. 查询

我们这里为查询结果的表格重新定义一个 TableViewController,这样可以分成”所有Student“和”查询到的student“这样两个集合。

7.1 新建 SearchResultTableViewController

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

7.2 定义两个变量来接收 allStudents 和 filterStudents
    var allStudents:[Student] = []
    var filterStudents:[Student] = []
7.3 将”列表“页面中的 cell 注册进该 TableViewController
    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "masterCell")
    }
7.4 设定 Cell 的个数
    // MARK: - Table view data source
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return filterStudents.count
    }
7.5 设定每个 Cell 的呈现方式
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "masterCell", for: indexPath)
        cell.textLabel?.text = filterStudents[indexPath.row].name
        return cell
    }
7.6 添加 SearchBar

在 MasterTableViewController.swift 中

    
    var searchController:UISearchController!		//全局变量

    func initSearch(){
        let resultsController = SearchResultTableViewController()  //结果控件
        resultsController.allStudents = self.jasonClass.Records
        
        searchController = UISearchController(searchResultsController: resultsController)
        
        let searchBar = searchController.searchBar
        searchBar.scopeButtonTitles = ["All","Name","Address"]   //设置 3 个搜索选项
        searchBar.placeholder = "Enter a search item"            //搜索文本框的默认文字
        searchBar.autocapitalizationType = UITextAutocapitalizationType.none  //关闭首字母自动大写
        searchBar.sizeToFit()                                    //设置大小
        tableView.tableHeaderView = searchBar   //把searchBar 加在 tableView 的上面
        
        searchController.searchResultsUpdater = resultsController  //结果
        self.definesPresentationContext = true  //指示当视图控制器或其后代之一提供视图控制器时是否覆盖该视图控制器的视图 ->可以跳转到详细页面
    }

还需要让界面加载的时候初始化 SearchBar

这里也是在 MasterTableViewController.swift 中

    override func viewDidLoad() {
        super.viewDidLoad()

        //读取数据到 Records[] 中
        jasonClass.Load()
        
        //初始化SearchBar及其查询功能
        initSearch()
    }
7.7 添加为SearchResultsTableViewController 添加 UISearchResultsUpdating 协议

为了实现查询的功能,SearchResultsTableViewController 需要遵循 UISearchResultsUpdating 协议

7.8 出现报错,点击 Fix,获得遵循 UISearchResultsUpdating 协议需要实现的方法
    func updateSearchResults(for searchController: UISearchController) {
        <#code#>
    }
7.9 编写 updateSearchResults 方法
    func updateSearchResults(for searchController: UISearchController) {
        
        //获取搜索框输入的内容
        guard let searchString = searchController.searchBar.text else{return}
        if searchString.isEmpty{return}
        
        switch searchController.searchBar.selectedScopeButtonIndex {
        case 0: //All
            filterStudents = allStudents.filter{ (stu)->Bool in
                return stu.name.contains(searchString)||stu.address.contains(searchString)
            }
        case 1: //Name
            filterStudents = allStudents.filter{(stu)->Bool in
                return stu.name.contains(searchString)
            }
        case 2: //Address
            filterStudents = allStudents.filter{ (stu)->Bool in
                return stu.address.contains(searchString)
            }
        default:
            return
        }
        
        //重新加载数据
        tableView.reloadData()
    }
7.10 在master控制器的prepareForUnwind函数的jasonClass.Save()后面加上initSearch()函数

搜索界面只在程序一开始初始化过,我们在 Add 后 allstudents 没有更新,所以在每次返回的时候不仅要save json,还要initsearch(因为swift的Array是结构类型,而且数组append后会生成一个新的数组)。

    //转场回来前要执行的函数
    @IBAction func prepareForUnwind(segue:UIStoryboardSegue){
        /**
         这里是源 source
         上面的prepare的目的地 destination
         */
        guard let detailViewController = segue.source as? DetailViewController else {return}
        
        //把新添加的Student添加到文件中
        if detailViewController.isAdd == true{
            jasonClass.Records.append(detailViewController.studnet!)
        }
        //写
        jasonClass.Save()
        
        //每次返回的时候不仅要save json,还要initsearch。
        initSearch()
    }

到这里我们只差一步了,就是在查询之后,点击其中的一个Cell,可以跳转到”详细“页面。因为我们这里的TableViewController是一个新的TableViewController,所以它没有绑定之前我们绑定的转场。

7.11 为”详细“页面设置辨识 storyboard ID

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

7.12 在 SearchResultsTableViewController 中添加下列函数

直接输入 didSelectRowAt 就会有提示。

    //点击选中的Cell,跳转到”详细“页面
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        let mainstoryboard = UIStoryboard(name: "Main", bundle: nil)//获取 Main.storyboard 对象
        let detailVC = mainstoryboard.instantiateViewController(identifier: "detailVC") as! DetailViewController //获取 Main.storyboard 中的 detailVC 界面对象
        
        let nav = self.presentingViewController?.navigationController  //把当前的VC压到NavigationController的栈当中
        
        detailVC.studnet = filterStudents[indexPath.row]  //设定”详细“页面中当前的student对象
        detailVC.isAdd = false  //很显然这里是我们查询到的结果,肯定不是 Add,而是 Edit
        nav?.pushViewController(detailVC, animated: true) //实现跳转到”详细“页面中
    }
7.13 效果演示

Swift — UIKit 之(6)—— Model 的 CRUD(文件版)

相关标签: Swift iOS UIKit