Я бы предложил модель структуры для захвата разделов табличного представления. Например:
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
}
Тогда база данных примерно так ...
... даст таблица выглядит так:
Так что 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 несколько других наблюдений:
Обязательно 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
документацию , которая указывает c на данный момент.
Я бы с осторожностью использовал *
в 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
префиксы.