Swift — UIKit 之(6)—— Model 的 CRUD(文件版)
文章目录
- 0. 本篇重点
- 1. 项目整体框架
- 1.1 新建一个 Single View App
- 1.2 在 Main.storyboard 中添加一个 Navigation Controller
- 1.3 更改根界面
- 1.4 给详细页面的 ViewController 更名(第三个界面,最右边)
- 1.5 为“列表”界面添加 UIViewTableController 并关联
- 1.6 设置 Cell 到“详细“页面的 Edit 转场
- 1.7 更改列表名 ![image-20200511102334806](https://imgconvert.csdnimg.cn/aHR0cHM6Ly90dmExLnNpbmFpbWcuY24vbGFyZ2UvMDA3UzhaSWxneTFnZW9hcnVncW1vajMxeDYwZW9xYnguanBn?x-oss-process=image/format,png)
- 1.8 在列表标题右边添加 Add 入口 Bar Button Item
- 1.9 更改 Bar Button Item 样式为 ”Add“
- 1.10 添加 Add 转场
- 1.11 ”详细“页面添加一个 Button
- 1.12 为”列表“界面的控制器添加 prepareForUnwind 函数来执行转场回来的时候要执行的事情
- 1.13 在”详细“页面选中绑定这个函数
- 1.14 为这个转场设置辨识ID
- 1.15 在 ”Save“ 按钮函数中指定这个函数
- 2. 定义 Student Model
- 3. 编写通用的 Json 存储文件类
- 4. 在 MasterTableViewController 中使用上面两个类
- 4.1 定义处理 Student 类的 Json 处理器
- 4.2 在”列表“页面加载是时候读取数据
- 4.3 在点击”Save“按钮转场回来的时候进行保存数据
- 4.4 指定”列表“界面显示多少行(这里我们没有分区)
- 4.5 设置列表中每个 Cell 的呈现方式
- 5. 添加和修改
- 5.1 设计”详细“页面
- 5.2 编写 DetailViewController.swift
- 5.3 转场进入”详细“页面 —— 判断是 Edit 还是 Add
- 5.4 从”详细“页面转场到”列表“页面 —— 把新 Add 的Student 添加到文件中
- 5.5 刷新数据
- 5.6 效果演示
- 6. 删除
- 7. 查询
- 7.1 新建 SearchResultTableViewController
- 7.2 定义两个变量来接收 allStudents 和 filterStudents
- 7.3 将”列表“页面中的 cell 注册进该 TableViewController
- 7.4 设定 Cell 的个数
- 7.5 设定每个 Cell 的呈现方式
- 7.6 添加 SearchBar
- 7.7 添加为SearchResultsTableViewController 添加 UISearchResultsUpdating 协议
- 7.8 出现报错,点击 Fix,获得遵循 UISearchResultsUpdating 协议需要实现的方法
- 7.9 编写 updateSearchResults 方法
- 7.10 在master控制器的prepareForUnwind函数的jasonClass.Save()后面加上initSearch()函数
- 7.11 为”详细“页面设置辨识 storyboard ID
- 7.12 在 SearchResultsTableViewController 中添加下列函数
- 7.13 效果演示
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 更改根界面
效果如下:
1.4 给详细页面的 ViewController 更名(第三个界面,最右边)
1.5 为“列表”界面添加 UIViewTableController 并关联
1.6 设置 Cell 到“详细“页面的 Edit 转场
并设置辨识ID
1.7 更改列表名
1.8 在列表标题右边添加 Add 入口 Bar Button Item
1.9 更改 Bar Button Item 样式为 ”Add“
1.10 添加 Add 转场
并设置辨识ID
1.11 ”详细“页面添加一个 Button
1.12 为”列表“界面的控制器添加 prepareForUnwind 函数来执行转场回来的时候要执行的事情
当”详细“页面点击Save的时候,我们需要做三件事:
- 保存 Model 信息
- 返回到”列表“页面
- 将保存的信息显示到”列表“页面
因为”列表“界面保存了很多条 Student 的信息,所以我们将保存操作交给”列表“页面
1.13 在”详细“页面选中绑定这个函数
1.14 为这个转场设置辨识ID
1.15 在 ”Save“ 按钮函数中指定这个函数
这样我们在从”详细“页面点击 ”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 设计”详细“页面
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 效果演示
6. 删除
6.1 开启 MasterTableViewController.swift 预置的关于 Edit 的两个函数
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 效果演示
7. 查询
我们这里为查询结果的表格重新定义一个 TableViewController,这样可以分成”所有Student“和”查询到的student“这样两个集合。
7.1 新建 SearchResultTableViewController
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
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) //实现跳转到”详细“页面中
}