Как заставить Tableview Sections работать с локальной базой данных SQLite в Swift 5 - PullRequest
0 голосов
/ 23 апреля 2020

Я новичок в Xcode и быстро учусь. Я пытаюсь создать приложение с базой данных SQLite, которая сохраняется в приложении. База данных имеет пять полей, а именно; id, cdcommand, cdtable, cdshortcut, cddescription ; Моя таблица загружается полностью нормально. Сейчас я пытаюсь добавить разделы в таблицу. Я хочу разделить данные на разделы из поля базы данных с именем 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
}
}

1 Ответ

0 голосов
/ 24 апреля 2020

Я бы предложил модель структуры для захвата разделов табличного представления. Например:

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
    }
}

Лично, только потому что ваша таблица использует столбец crypti c Имена не означают, что ваши Shortcuts тоже должны их использовать. Я бы также удалил s с конца Shortcuts, потому что каждый объект представляет один ярлык:

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:) подпрограмма для заполнения структуры модели для вашей сгруппированной таблицы. Таким образом, учитывая:

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) })

        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 { $0.table }
        .map { Section(name: $0.0, elements: $0.1) }
}

func errorMessage() -> String {
    return sqlite3_errmsg(db)
        .flatMap { String(cString: $0) } ?? "Unknown error"
}

A несколько других наблюдений:

  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 документацию , которая указывает c на данный момент.

  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 префиксы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...