如何在Swift 5中使用SQLite本地数据库获取Tableview节

问题描述 投票:0回答:1

我是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
}
}
swift xcode sqlite sections uitableviewsectionheader
1个回答
0
投票

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

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
}

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

enter image description here

...将产生一个像这样的表:

enter image description here


所以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"
}

其他一些意见:

  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,因为分发包中的文档是只读的。

  3. 坦白说,我们通常不会从捆绑包中打开,因为这是只读的。通常,我们会从应用程序支持目录之类的地方打开,如果找不到,请从捆绑包中复制:

    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语句中使用*。代码的正确性不应取决于表中各列的顺序。因此,而不是:

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

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

    let queryString = "SELECT id, cdcommand, cdtable, cdshortcut, cddescription FROM main ORDER BY cdtable, cdcommand"
    
  6. 我是否从表中的cd前缀推断出您正在处理CoreData数据库?花了所有时间讨论SQLite,如果您使用的是CoreData,建议您不要使用它(尤其是如果您打算以后进行更新)。现在,如果您放弃了CoreData,那很好。但是之后我会丢失cd前缀。

© www.soinside.com 2019 - 2024. All rights reserved.