如何在 Swift 5 中让 Tableview 部分与 SQLite 本地数据库一起工作

How to get Tableview Sections working with SQLite local database in Swift 5

我是 Xcode 和 swift 的新手,正在学习。我正在尝试使用保存在应用程序中的 SQLite 数据库构建一个应用程序。该数据库有五个字段,即; id, cdcommand, cdtable, cdshortcut, cddescription;我的 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
}
}

我建议使用模型结构来捕获 table 视图的各个部分。例如:

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 { [=11=].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
    }
}

就个人而言,仅仅因为您的 table 使用的是隐含的列名称并不意味着您的 Shortcuts 也应该使用它们。我还要从 Shortcuts 的末尾删除 s 因为每个 object 代表一个快捷方式:

class Shortcut {
    let id: Int
    let command: String?
    let table: String?
    let shortcut: String?
    let description: String?
    let image: UIImage?

    init(id: Int, command: String?, table: String?, shortcut: String?, description: String?, image: UIImage?) {
        self.id = id
        self.command = command
        self.table = table
        self.shortcut = shortcut
        self.description = description
        self.image = image
    }
}

然后,您的读取例程可以读取结果,并调用此 grouped(by:) 例程来填充分组 table 的模型结构。因此,给定:

var sections: [Section<String, Shortcut>] = []

您可以填充它:

sections = shortcuts.grouped { [=14=].table }
    .map { Section(name: [=14=].0, elements: [=14=].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
}

然后像这样的数据库...

... 会产生一个 table 像这样:


所以 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: [=17=]) })
        let table       = sqlite3_column_text(statement, 2).flatMap({ String(cString: [=17=]) })
        let shortcut    = sqlite3_column_text(statement, 3).flatMap({ String(cString: [=17=]) })
        let description = sqlite3_column_text(statement, 4).flatMap({ String(cString: [=17=]) })

        let count = Int(sqlite3_column_bytes(statement, 5))
        var image: UIImage?
        if count > 0, let bytes = sqlite3_column_blob(statement, 5) {
            let data = Data(bytes: bytes, count: count)
            image = UIImage(data: data)
        }

        shortcuts.append(Shortcut(id: id, command: command, table: table, shortcut: shortcut, description: description, image: image))
    }

    sections = shortcuts.grouped { [=17=].table }
        .map { Section(name: [=17=].0, elements: [=17=].1) }
}

func errorMessage() -> String {
    return sqlite3_errmsg(db)
        .flatMap { String(cString: [=17=]) } ?? "Unknown error"
}

其他一些观察结果:

  1. 确保 sqlite3_finalize 每个成功准备的语句。​​

  2. 您正在从包中打开数据库。通常你不会那样做。但如果你是,我可能会建议这样打开它:

    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,因为捆绑包中的文档是 read-only。

  3. 老实说,我们一般不会从捆绑包中打开,因为那是read-only。我们通常会从某个地方打开,比如应用程序支持目录,如果我们找不到它,就从包中复制:

    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
    }
    
  4. 请注意,在这两种选择中,如果打开失败,请始终关闭数据库。我知道这看起来很奇怪,但是看到sqlite3_open documentation,这是关于这一点的具体说明。

  5. 我对在 SQL 语句中使用 * 持谨慎态度。您的代码的正确性不应取决于 table 中列的顺序。所以,而不是:

    let queryString = "SELECT * FROM main ORDER BY cdtable, cdcommand"
    

    相反,我建议明确列的顺序:

    let queryString = "SELECT id, cdcommand, cdtable, cdshortcut, cddescription FROM main ORDER BY cdtable, cdcommand"
    
  6. 我是否从您 table 中的 cd 前缀推断出您正在处理 CoreData 数据库?花了这么多时间谈论 SQLite,如果你正在使用 CoreData,我建议留在里面(特别是如果你打算稍后更新它)。现在,如果您放弃了 CoreData,那很好。但是我会丢失 cd 前缀。