Swift+SQLite3开发学生数据存储APP的查询显示异常求助
解决Swift SQLite学生数据显示异常问题
我看到你遇到的问题了——这是因为在绑定SQLite参数的时候,你传递了一个可选类型的字符串,导致SQLite没有正确读取姓名的值,反而意外获取到了日期字段的部分内容。让我一步步帮你解决这个问题:
问题根源分析
在buttonSave方法中,你获取的name是Optional<String>类型,直接传递给sqlite3_bind_text时,Swift的自动桥接无法正确处理可选值,导致SQLite没有正确绑定姓名参数。这就使得数据库中name字段被意外填充了date字段的部分内容,最终在列表中显示出日期的切片。
另外,你的代码还有两个额外的小问题:
viewDidLoad中每次启动都会执行DROP TABLE Students,导致之前存储的所有数据丢失cellForRowAt方法没有复用UITableViewCell,会影响滚动性能
具体修复步骤
1. 修复SQLite参数绑定的可选解包
在绑定姓名参数时,因为你已经通过非空判断确认了姓名有效,所以可以安全地解包name,同时建议将最后一个参数改为SQLITE_TRANSIENT,让SQLite自动复制字符串内容,避免Swift字符串被释放后出现内存问题:
// 原姓名绑定代码 if sqlite3_bind_text(stmt, 1, name, -1, nil) != SQLITE_OK{ // 修改为 if sqlite3_bind_text(stmt, 1, name!, -1, SQLITE_TRANSIENT) != SQLITE_OK{
日期绑定也做同样修改:
// 原日期绑定代码 if sqlite3_bind_text(stmt, 2, date, -1, nil) != SQLITE_OK{ // 修改为 if sqlite3_bind_text(stmt, 2, date, -1, SQLITE_TRANSIENT) != SQLITE_OK{
2. 移除不必要的表删除语句
删除viewDidLoad中的DROP TABLE Students语句,这样重启APP后之前存储的数据不会丢失:
// 移除以下代码块 // if sqlite3_exec(db, "DROP TABLE Students", nil, nil, nil) != SQLITE_OK { // let errmsg = String(cString: sqlite3_errmsg(db)!) // print("error deleting table: \(errmsg)") // }
3. 优化UITableViewCell复用
在cellForRowAt方法中使用单元格复用,提升滚动性能:
// 原代码 let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell") // 修改为 let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) ?? UITableViewCell(style: .default, reuseIdentifier: "cell")
同时记得在Storyboard中给你的TableViewCell设置reuseIdentifier为"cell",确保复用正常工作。
4. 可选:优化日期存储(推荐)
目前你将日期存储为字符串,推荐存储时间戳(Int64),这样后续处理日期会更灵活:
// 存储时 let timestamp = Int64(Date().timeIntervalSince1970) // 绑定参数时用sqlite3_bind_int64 if sqlite3_bind_int64(stmt, 2, timestamp) != SQLITE_OK{ let errmsg = String(cString: sqlite3_errmsg(db)!) print("failure binding date: \(errmsg)") return } // 读取时 let timestamp = sqlite3_column_int64(stmt, 2) let date = Date(timeIntervalSince1970: TimeInterval(timestamp)) // 再转换为需要的字符串格式 let formatter = DateFormatter() formatter.dateFormat = "dd.MM.yyyy" let dateString = formatter.string(from: date)
修改后的完整代码
import UIKit import SQLite3 class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { var db: OpaquePointer? var Students = [Student]() @IBOutlet weak var textFieldName: UITextField! @IBAction func buttonSave(_ sender: UIButton) { // 使用guard let更安全地处理可选值 guard let name = textFieldName.text?.trimmingCharacters(in: .whitespacesAndNewlines), !name.isEmpty else { textFieldName.layer.borderColor = UIColor.red.cgColor return } let d = Date() let formatter = DateFormatter() formatter.dateFormat = "dd.MM.yyyy" let date = formatter.string(from: d) var stmt: OpaquePointer? let queryString = "INSERT INTO Students (name, date) VALUES (?,?)" if sqlite3_prepare(db, queryString, -1, &stmt, nil) != SQLITE_OK{ let errmsg = String(cString: sqlite3_errmsg(db)!) print("error preparing insert: \(errmsg)") return } if sqlite3_bind_text(stmt, 1, name, -1, SQLITE_TRANSIENT) != SQLITE_OK{ let errmsg = String(cString: sqlite3_errmsg(db)!) print("failure binding name: \(errmsg)") return } if sqlite3_bind_text(stmt, 2, date, -1, SQLITE_TRANSIENT) != SQLITE_OK{ let errmsg = String(cString: sqlite3_errmsg(db)!) print("failure binding date: \(errmsg)") return } if sqlite3_step(stmt) != SQLITE_DONE { let errmsg = String(cString: sqlite3_errmsg(db)!) print("failure inserting student: \(errmsg)") return } textFieldName.text="" readValues() print("Student saved successfully") } @IBOutlet weak var tableViewStudents: UITableView! override func viewDidLoad() { super.viewDidLoad() let fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) .appendingPathComponent("StudentsDatabase.sqlite") if sqlite3_open(fileURL.path, &db) != SQLITE_OK { print("error opening database") } if sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS Students (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, date TEXT)", nil, nil, nil) != SQLITE_OK { let errmsg = String(cString: sqlite3_errmsg(db)!) print("error creating table: \(errmsg)") } // 初始化时读取已有数据 readValues() } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return Students.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) ?? UITableViewCell(style: .default, reuseIdentifier: "cell") let student = Students[indexPath.row] // 同时显示姓名和日期,方便验证结果 cell.textLabel?.text = "\(student.name) - \(student.date)" return cell } func readValues(){ Students.removeAll() let queryString = "SELECT * FROM Students" var stmt:OpaquePointer? if sqlite3_prepare(db, queryString, -1, &stmt, nil) != SQLITE_OK{ let errmsg = String(cString: sqlite3_errmsg(db)!) print("error preparing select: \(errmsg)") return } while(sqlite3_step(stmt) == SQLITE_ROW){ let id = sqlite3_column_int(stmt, 0) let name = String(cString: sqlite3_column_text(stmt, 1)) let date = String(cString: sqlite3_column_text(stmt, 2)) Students.append(Student(id: Int(id), name: name, date: date)) } tableViewStudents.reloadData() } } class Student { var id: Int var name: String var date: String init(id: Int, name: String, date: String){ self.id = id self.name = name self.date = date } }
额外提示
- 建议使用
guard let来处理可选值,比强制解包更安全(我已经在修改后的代码中替换了原有的可选判断) - 可以考虑封装SQLite操作到单独的工具类中,让代码结构更清晰
内容的提问来源于stack exchange,提问作者Valeria S.
相关产品推荐
相关产品推荐

