我是Xcode的新手,敏捷而又学习。我正在尝试使用保存在应用程序内的SQLite数据库来构建应用程序。该数据库有五个字段,即: id,cdcommand,cdtable,cdshortcut,cddescription;我的tableview加载完全正常。现在,我试图将部分添加到tableview。我想将数据从名为cdtable的数据库字段中划分为多个部分。我正在努力做到正确。
numberofrowsinSection被完全破坏,UITableView,cellForRowAt indexPath:IndexPath也被破坏)。两者都显示出奇怪的结果。
我设法以某种方式正确解决的唯一事情是numberOfSections和viewForHeaderInSection。
任何人都可以指导如何使它正常工作吗?下面是我正在使用的代码...
我的代码从2个文件中共享。预先感谢。
File1.swift
class Shortcuts {
var id: Int
var cdcommand: String?
var cdtable: String?
var cdshortcut: String?
var cddescription: String?
init(id: Int, cdcommand: String?, cdtable: String?, cdshortcut: String?, cddescription: String?){
self.id = id
self.cdcommand = cdcommand
self.cdtable = cdtable
self.cdshortcut = cdshortcut
self.cddescription = cddescription
}
}
class tableNames {
var cdtable: String?
init(cdtable: String?){
self.cdtable = cdtable
}
}
File2.swift
import Foundation
import UIKit
import SQLite3
class shortcutCustomCell: UITableViewCell {
@IBOutlet weak var commandText: UILabel!
@IBOutlet weak var tableText: UILabel!
@IBOutlet weak var shortcutText: UILabel!
@IBOutlet weak var descText: UITextView!
}
class shortcutsController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
var db: OpaquePointer?
var shortcutList = [Shortcuts]()
var tablenameList = [tableNames]()
@IBOutlet weak var tableViewShortcuts: UITableView!
@IBOutlet weak var searchBar: UISearchBar!
@IBAction func unwindToPreviousViewController(_ sender: UIBarButtonItem) {
self.navigationController?.popViewController(animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let appearance = UINavigationBarAppearance()
appearance.titleTextAttributes = [.foregroundColor: UIColor.white]
appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
appearance.backgroundColor = UIColor(red:0.79, green:0.37, blue:0.31, alpha:1.00)
navigationItem.standardAppearance = appearance
navigationItem.scrollEdgeAppearance = appearance
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
//Search Begin
searchBar.delegate = self
searchBar.searchTextField.backgroundColor = .white
searchBar.barTintColor = UIColor.darkGray//(red: 0.79, green: 0.37, blue: 0.31, alpha: 1.00)
searchBar.searchTextField.textColor = .darkGray
//Search End
//SQLite Connection Begin
let fileMgr = FileManager.default
let dbPath = URL(fileURLWithPath: Bundle.main.resourcePath ?? "").appendingPathComponent("database.db")
let pathString = dbPath.path
let success = fileMgr.fileExists(atPath: pathString)
if !success {
print("Cannot locate database file '\(dbPath)'.")
}
if !(sqlite3_open(dbPath.absoluteString, &db) == SQLITE_OK) {
print("An error has occured.")
}
readValues()
queryAlltableNames()
//SQLite Connection End
}
func readValues(){
shortcutList.removeAll()
let queryString = "SELECT * FROM main ORDER BY cdtable, cdcommand ASC"
var stmt:OpaquePointer?
if sqlite3_prepare(db, queryString, -1, &stmt, nil) != SQLITE_OK{
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("error preparing insert: \(errmsg)")
return
}
while(sqlite3_step(stmt) == SQLITE_ROW){
let id = sqlite3_column_int(stmt, 0)
let cdcommand = String(cString: sqlite3_column_text(stmt, 1))
let cdtable = String(cString: sqlite3_column_text(stmt, 2))
let cdshortcut = String(cString: sqlite3_column_text(stmt, 3))
let cddescription = String(cString: sqlite3_column_text(stmt, 4))
shortcutList.append(Shortcuts(id: Int(id), cdcommand: String(describing: cdcommand), cdtable: String(describing: cdtable), cdshortcut: String(describing: cdshortcut), cddescription: String(describing: cddescription)))
}
self.tableViewShortcuts.reloadData()
}
func queryAlltableNames() {
let queryTableNames = "SELECT DISTINCT cdtable FROM main ORDER BY cdtable ASC"
var stmt:OpaquePointer?
if sqlite3_prepare(db, queryTableNames, -1, &stmt, nil) != SQLITE_OK{
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("error preparing insert: \(errmsg)")
return
}
while(sqlite3_step(stmt) == SQLITE_ROW){
let cdtable = String(cString: sqlite3_column_text(stmt, 0))
tablenameList.append(tableNames(cdtable: String(describing: cdtable)))
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return shortcutList[section].cdtable!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellShortcuts", for: indexPath) as! shortcutCustomCell
let shortcut: Shortcuts
shortcut = shortcutList[indexPath.row]
cell.commandText.text = shortcut.cdcommand
cell.shortcutText.text = shortcut.cdshortcut
cell.descText.text = shortcut.cddescription
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return tablenameList.count
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 30))
view.backgroundColor = UIColor.black
let lbl = UILabel(frame: CGRect(x: 15, y: 0, width: view.frame.width - 15, height: 30))
lbl.font = UIFont.boldSystemFont(ofSize: 16)
lbl.textColor = UIColor.white
lbl.text = tablenameList[section].cdtable
view.addSubview(lbl)
return view
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 30
}
}
我建议使用一种模型结构来捕获表格视图的各个部分。例如:
class Section<Key, Element> {
let name: Key?
let elements: [Element]
init(name: Key?, elements: [Element]) {
self.name = name
self.elements = elements
}
}
然后您可以编写例程以遍历已排序的数组并将其分组:
extension Sequence {
/// Group sorted array.
///
/// This assumes the array is already sorted by whatever you will be grouping it.
///
/// let input = [Foo(bar: "a", baz: "x"), Foo(bar: "a", baz: "y"), Foo(bar: "b", baz: "z")]
/// let result = input.grouped { $0.bar }
/// // [
/// // ("a", [Foo(bar: "a", baz: "x"), Foo(bar: "a", baz: "y")]),
/// // ("b", [Foo(bar: "b", baz: "z")])
/// // ]
///
/// - Parameter groupedBy: Closure to dictate how it will be grouped.
///
/// - Returns: An array of tuples, one entry for every new occurenc of the `groupedBy` result.
func grouped<Key: Equatable>(by groupedBy: (Element) -> Key) -> [(Key, [Element])] {
var results: [(Key, [Element])] = []
var previousKey: Key?
var previousElements: [Element] = []
for element in self {
let key = groupedBy(element)
if key != previousKey {
if let previousKey = previousKey {
results.append((previousKey, previousElements))
}
previousKey = key
previousElements = []
}
previousElements.append(element)
}
if let previousKey = previousKey {
results.append((previousKey, previousElements))
}
return results
}
}
个人而言,仅因为您的表使用的是隐秘的列名,并不意味着您的Shortcuts
也应使用它们。我还要从s
的末尾删除Shortcuts
,因为每个对象代表一个快捷键:
class Shortcut {
let id: Int
let command: String?
let table: String?
let shortcut: String?
let description: String?
init(id: Int, command: String?, table: String?, shortcut: String?, description: String?) {
self.id = id
self.command = command
self.table = table
self.shortcut = shortcut
self.description = description
}
}
然后,您的读取例程可以读取结果,并调用此grouped(by:)
例程以填充分组表的模型结构。因此,给定:
var sections: [Section<String, Shortcut>] = []
您可以使用以下填充:
sections = shortcuts.grouped { $0.table }
.map { Section(name: $0.0, elements: $0.1) }
然后您的数据源方法将如下所示:
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].elements.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let shortcut = sections[indexPath.section].elements[indexPath.row]
// configure your cell however you want
cell.textLabel?.text = shortcut.command
cell.detailTextLabel?.text = shortcut.shortcut
return cell
}
并获得该部分的标题,如下所示:
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section].name
}
然后是这样的数据库...
...将产生一个像这样的表:
所以readValues
可能看起来像:
var sections: [Section<String, Shortcut>] = []
var shortcuts: [Shortcut] = []
func readValues() {
let queryString = "SELECT * FROM main ORDER BY cdtable, cdcommand"
var statement: OpaquePointer?
guard sqlite3_prepare(db, queryString, -1, &statement, nil) == SQLITE_OK else {
print("error preparing select: \(errorMessage())")
return
}
defer { sqlite3_finalize(statement) }
shortcuts = []
while sqlite3_step(statement) == SQLITE_ROW {
let id = Int(sqlite3_column_int(statement, 0))
let command = sqlite3_column_text(statement, 1).flatMap({ String(cString: $0) })
let table = sqlite3_column_text(statement, 2).flatMap({ String(cString: $0) })
let shortcut = sqlite3_column_text(statement, 3).flatMap({ String(cString: $0) })
let description = sqlite3_column_text(statement, 4).flatMap({ String(cString: $0) })
shortcuts.append(Shortcut(id: id, command: command, table: table, shortcut: shortcut, description: description))
}
sections = shortcuts.grouped { $0.table }
.map { Section(name: $0.0, elements: $0.1) }
}
func errorMessage() -> String {
return sqlite3_errmsg(db)
.flatMap { String(cString: $0) } ?? "Unknown error"
}
其他一些意见:
请确保sqlite3_finalize
每个成功准备的语句。
您正在从分发包中打开数据库。通常,您不会这样做。但是,如果您愿意,我建议您像这样打开它:
func openDatabase() -> Bool {
guard let dbPath = Bundle.main.url(forResource: "database", withExtension: "db") else {
print("Cannot locate database file.")
return false
}
guard sqlite3_open_v2(dbPath.path, &db, SQLITE_OPEN_READONLY, nil) == SQLITE_OK else {
print("An error has occurred.", errorMessage())
sqlite3_close(db)
return false
}
return true
}
Bundle.main.url(forResource:withExtension:)
将为您检查是否存在。
注意:我使用SQLITE_OPEN_READONLY
,因为分发包中的文档是只读的。
坦白说,我们通常不会从捆绑包中打开,因为这是只读的。通常,我们会从应用程序支持目录之类的地方打开,如果找不到,请从捆绑包中复制:
func openDatabase() -> Bool {
let applicationSupportURL = try! FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("database.db")
if sqlite3_open_v2(applicationSupportURL.path, &db, SQLITE_OPEN_READWRITE, nil) == SQLITE_OK {
return true
}
// if we got here, we were unable to open database, so we'll copy it from the bundle
sqlite3_close(db)
guard let bundleURL = Bundle.main.url(forResource: "database", withExtension: "db") else {
print("Cannot locate database file.")
return false
}
do {
try FileManager.default.copyItem(at: bundleURL, to: applicationSupportURL)
} catch {
print(error)
return false
}
guard sqlite3_open_v2(applicationSupportURL.path, &db, SQLITE_OPEN_READWRITE, nil) == SQLITE_OK else {
print("An error has occurred.", errorMessage())
sqlite3_close(db)
return false
}
return true
}
在这两种选择中均请注意,如果打开失败,请始终关闭数据库。我知道这似乎很奇怪,但是请参见sqlite3_open
documentation,这是特定于此的。
我会警惕在SQL语句中使用*
。代码的正确性不应取决于表中各列的顺序。因此,而不是:
let queryString = "SELECT * FROM main ORDER BY cdtable, cdcommand"
相反,我建议您明确说明列的顺序:
let queryString = "SELECT id, cdcommand, cdtable, cdshortcut, cddescription FROM main ORDER BY cdtable, cdcommand"
我是否从表中的cd
前缀推断出您正在处理CoreData数据库?花了所有时间讨论SQLite,如果您使用的是CoreData,建议您不要使用它(尤其是如果您打算以后进行更新)。现在,如果您放弃了CoreData,那很好。但是之后我会丢失cd
前缀。