Möchte man in seiner App große Datenmengen auf einem iDevice speichern oder auslesen, so kommt man um ein Datenbanksystem nicht herum. Für iDevices bietet sich da die Programmbibliothek SQLite an. In diesem Tutorial werden wir eine einfach App entwickeln, welche die benötigten Daten aus einer SQLite-Datenbank lädt. Sie soll eine Art Einkaufsliste darstellen, die verschiedene Produkte enthält.
1. SQLite
Während man kleinere Dateimengen meist in sogenannten Property Lists abspeichert, verwendet man, wie bereits erwähnt, für große Datenmenge eine SQLite-Datenbank. Diese hat den Vorteil, dass man leicht ganz bestimmte Datensätze durch SQL-Abfragen erhält. Das bedeutet man hat schnellen Zugriff auf lediglich die benötigten Informationen und muss nicht den ganzen Datensatz auslesen und dann die gewollten Daten herausfiltern.
Allerdings stellt Apple selbst ein Framework zur Verfügung, mit dem es möglich ist mit SQLite-Datenbanken zu arbeiten, nämlich Core Data. Mittlerweile verwendet man dieses Framework zwar bevorzugt gegenüber der standardmäßigen sqlite3-Library, dennoch schadet es nicht, mit SQLite3 zu beginnen, da die Einstiegshürde für Core Data etwas höher liegt, als bei SQLite3. Noch dazu hat bestimmt schon der ein oder andere mit SQLite als Datenbanksystem gearbeitet und kennt sich deshalb schon mit den Abfragen etc. aus. Auch wenn man mittlerweile primär Core Data verwendet, befindet sich die sqlite3-Library immer noch im SDK, was bedeutet, dass wir kein externes Framework downloaden müssen.
Ich werde in diesem Tutorial auch die benötigten SQLite-Abfragen erklären, allerdings nicht so ausführlich wie es bei einem für SQLite selbst geschriebenem Tutorial der Fall wäre. Dieses Tutorial setzt außerdem nicht voraus, dass Du SQL perfekt beherrschst.
Zu erwähnen bleibt noch, dass es sich bei dem sqlite3-Framework um ein in der Programmiersprache C geschriebenes Framework handelt. Das bedeutet, dass wir es mit C-Methoden zutun haben und dass Zeichenketten im UTF8-Format übergeben werden. Das klingt etwas kompliziert, ist es aber gar nicht; ich werde, wenn es so weit ist, darauf eingehen.
Das soweit zu den allgemeinen Informationen über SQLite. Wer noch mehr wissen will, kann einfach die entsprechende Website besuchen: sqlite.org.
2. Eine Datenbank erstellen
Bevor wir mit dem Programmieren beginnen, müssen wir noch ein paar Vorarbeiten erledigen. Zuerst benötigen wir eine Datenbank, aus der wir später die Einkaufsgegenstände auslesen werden.
Dafür gibt es im Internet natürlich unzählige Programme; für unsere Zwecke reicht aber der Mac OS X-Terminal. Mit diesem ist es nämlich möglich mit Hilfe von SQL-Befehlen eine Datenbank zu erstellen und Daten einzufügen.
Der Terminal befindet sich im Application-Ordner im Dienstprogramme-Ordner. Wenn Du ihn öffnest sollte folgendes Fenster erscheinen:
Wir beginnen mit einen (noch nicht SQL-)Befehl, mit dem wir zu einem bestimmten Pfad auf dem Computer gelangen. Schreibe oder kopiere also folgenden Befehl in das Fenster:
cd Desktop/
Wenn Du den Befehl eingegeben hast, drücke Enter.
Der Pfad in diesem Befehl ist einfach der Desktop, also werden alle in dieser Terminal-Sitzung erstellten Dateien auf dem Desktop gespeichert.
Nachdem Du auf Enter gedrückt hast, erscheint eine neue Terminal-Zeile.
Als nächstes teilen wir dem Terminal mit, dass wir eine sqlite3-Datenbank erstellen wollen. Gib dazu folgenden Befehl ein:
sqlite3 shoppinglist.sqlite3
Hier legen wir bereits den Datenbanknamen fest: “shoppinglist.sqlite3″. Sobald Du Enter drückst, erscheinen einige Informationen über die aktuelle SQLite-Version und wie man Befehle eingibt. Außerdem steht nun am Anfang jeder neuen Zeile “sqlite>”.
Jetzt können wir erstmals SQLite-Befehle eingeben.
create table shoppinglist (uniqueId integer primary key autoincrement, title text, number integer, shop text);
Mit create table erstellen wir die Tabelle, in der wir alle Einkaufsgegenstände speichern. Wir nennen sie ganz banal “shoppinglist”. In den Klammern dahinter geben wir an, welche Spalten diese Tabelle enthalten soll. Jede Spalte stellt ein Attribut dar, welches einen Einkaufsgegenstand beschreibt.
Die erste Spalte wird mit uniqueId integer primary key autoincrement erstellt. uniqueId ist dabei einfach der Name der Spalte, integer der Datentyp, den der Wert eines Attributs haben soll. Integer ist der gleiche Datentyp wie int in Objective-C, also eine ganze Zahl.
Es fällt auf, dass diese erste Spalte kein richtiges Attribut eines realen Einkaufsgegenstandes ist (ein Apfel im Supermarkt besitzt selten einen uniqueId). Diese Spalte wird als Primärschlüssel bezeichnet. Jede Tabelle benötigt eine (oder mehrere) Spalten als Primärschlüssel. Dieser soll für jede Zeile in der Tabelle, also in unserem Fall für jeden Einkaufsgegenstand, eindeutig sein. Welche Spalte der Primärschlüssel sein soll, legen wir mit primary key fest. autoincrement gibt an, dass für jede neue Zeile, automatisch der Wert für die Spalte uniqueId, also unseren Primärschlüssel, erstellt werden soll. Wenn wir nun die erste Zeile hinzufügen, bekommt diese den uniqueId-Wert 1, die nächste 2 usw.
Danach erstellen wir noch drei weitere Spalten, bei denen wir aber nur den Namen und den Datentyp angeben müssen. Die Namen sollten selbsterklärend sein.
Soweit so gut. Wir haben jetzt eine Tabelle, die allerdings noch leer ist. Um den ersten Einkaufsgegenstand hinzuzufügen, verwenden wir folgenden Befehl:
insert into shoppinglist (title, number, shop) values ('Schreibblock', 2, 'Schreibwaren');
Nach der Befehlsbezeichnung insert into und dem Tabellennamen stehen in Klammern die Spaltennamen, die wir bei der neuen Zeile angeben möchten. Man kann nämlich auch einfach manche Spalten leer lassen, solange es sich dabei nicht um den Primärschlüssel handelt. Das Seltsame ist, dass bei unserem Befehl gerade der Primärschlüssel uniqueId fehlt
. Wie Du wahrscheinlich schon erraten hast, können wir ihn weglassen, da wir beim Erstellen der Tabelle mit autoincrement angegeben haben, dass er automatisch erstellt werden soll. Bei diesem ersten Gegenstand bekommt er somit den Wert 1.
Nach den Spaltennamen kommt das Stichwort values und dann die Werte, die in die jeweiligen Spalten eingetragen werden sollen. Dabei ist natürlich die Reihenfolge zu beachten. Außerdem werden Zeichenketten, anders als in Objective-C, von zwei Apostrophen eingeklammert. Gleich ist aber, dass nach jeder Anweisung ein Semikolon, also ein Strichpunkt steht.
Füge jetzt einfach nacheinander zwei weitere Gegenstände hinzu:
insert into shoppinglist (title, number, shop) values ('Milch', 4, 'Supermarkt');
Enter
insert into shoppinglist (title, number, shop) values ('Brot', 1, 'Bäcker');
Enter
Drei Datensätze reichen für den Anfang. Wir beenden die SQL-Eingabe mit dem Befehl
.quit
Das war’s auch schon. Hier mal ein Bild, wie die Tabelle shoppinglist visuell dargestellt aussieht (das verwendete Programm heißt MesaSQLite):
Falls das Erstellen der Datenbank aus irgendeinem Grund nicht geklappt hat, kannst Du sie hier downloaden: shoppinglist.sqlite3.
3. Das Projekt erstellen
Öffne Xcode und erstelle ein neues Projekt. Wähle dazu das Single View Application Template. Nenne das Projekt bspw. “SQLiteTutorial” und gehe sicher, dass Device Family auf “iPhone” eingestellt ist und der Haken nur bei Use Storyboards und Use Automatic Reference Counting gesetzt ist. Speichere das Projekt ab.
Als nächstes binden wir die sqlite3-Library in das Projekt ein. Erst mit Hilfe dieser Library können wir mit einer SQLite3-Datenbank arbeiten. Wie ich schon erwähnte, ist diese Library schon im SDK enthalten, das heißt wir können sie ganz einfach hinzufügen ohne sie irgendwo downloaden zu müssen:
Wähle im Project navigator die Projektdatei aus, wenn sie nicht bereits ausgewählt ist. Scrolle dann im Editor-Bereich nach unten, bis Du zu Linked Frameworks and Libraries kommst. Klicke dann auf das Plus-Symbol und gib oben im Suchfeld des erscheinenden Fensters “libsqlite3.0″ ein. Wähle dann das übrige Element aus und klicke auf Add.
Das war’s auch schon.
Zuletzt müssen wir noch unsere vorhin erstellte Datenbank zum Projekt hinzufügen. Dazu ziehst Du sie einfach vom Desktop aus in den Projektordner (die Datenbank wird auf deinem Desktop wahrscheinlich ein anderes Icon wie im Bild oder gar keines haben).
Im Drop-Down Fenster sollte der Haken bei Copy items into… und bei Add to targets –> SQLiteTutorial gesetzt sein.
Das war’s auch schon, jetzt können wir mit dem Coden anfangen.
4. Das Interface erstellen
Bevor wir allerdings richtig mit der Datenbank arbeiten, erstellen wir erstmal das Interface der App. Das wird ein einfacher Table View werden, in welchem dann die Einkaufsgegenstände angezeigt werden.
Öffne zuerst die Datei “MainStoryboard.storyboard” und lösche den View Controller im Editor-Bereich. Ziehe dann einen Table View Controller aus der Library in den Editor.
Wir bleiben noch im Storyboard, um unsere Prototype Cells zu erstellen. Wähle dazu die einzige Zelle im Table View aus und öffne den Attributes inspector. Stelle Style auf “Basic” und gib bei Identifier “ItemCell” ein.
Wähle zunächst die Dateien “ViewController.h” und “ViewController.m” im Project navigator aus und lösche sie (Rechtsklick->Delete->Move To Trash). Wir benötigen nämlich eine TableViewController-Klasse. Um diese zu erstellen, klickst Du mit der rechten Maustaste auf den Projektordner und dann auf New File… Wähle dann das Objective-C class-Template und gib bei Class “ShoppinglistViewController” ein. Stelle außerdem sicher, dass bei Subclass of “UITableViewController” ausgewählt ist. Ansonsten sollte kein Haken gesetzt sein. Klicke auf Next und speichere die Dateien ab.
Zuletzt müssen wir noch einmal die Datei “MainStoryboard.storyboard” öffnen. Wähle dort den Table View Controller aus, indem Du auf das schwarze Dock unter ihm klickst und gib bei Class im Identity inspector “ShoppinglistViewController” ein.
Das Interface ist fertig. Führe die App aus, um zu sehen, ob alles funktioniert. Es sollte einfach ein leerer Table View angezeigt werden.
5. Die Klasse DbConnect
Wir legen uns eine eigene Klasse für den Zugriff auf die Datenbank an. Diese Klasse enthält dann nicht nur die Instanz der Datenbank im Code, sondern auch Methoden, die diese ordnungsgemäß öffnen und schließen oder benötigte Informationen auslesen. Möchte man dann von einem View Controller aus auf die Datenbank zugreifen, verwendet man diese Methoden.
Erstelle also eine weitere Klasse. Diesmal sollte bei Subclass of allerdings NSObject eingestellt sein. Nenne die Klasse ”DbConnect”.
Zuerst binden wir eine Datei ein, die wir zum Arbeiten mit SQLite benötigen. Öffne “DbConnect.h” und füge folgende #import-Zeile unter der anderen ein:
#import "sqlite3.h"
Dann fügen wir drei Membervariablen zur Klasse hinzu. Öffne dazu die Datei “DbConnect.m” und ändere deren Inhalt wie folgt ab:
#import "DbConnect.h"
@implementation DbConnect {
sqlite3 *database;
NSString *dbPath;
sqlite3_stmt *statement;
}
@end
Wie ich bereits in einem anderen Tutorial erwähnt habe, schreibt man einfache Instanzvariablen (also nicht Properties) nicht mehr in die Header-Datei, sondern in geschweiften Klammern unter das @implementation in der .m-Datei. Der Grund dafür ist, dass die Header-Datei aufgeräumter bleiben soll, indem sich nur Properties und Methoden in ihr befinden.
Die erste Variable ist vom Typ sqlite3 und ist die Instanz der Datenbank. Wie wir mit dieser Variable arbeiten, siehst Du gleich.
In dbPath speichern wir ganz einfach den Namen der Datenbank.
Die Variable statement vom Typ sqlite3_stmt werden wir erst später benötigen, wenn es um die ersten Abfragen in der Tabelle geht. In ihr wird der SQL-Befehl gespeichert.
Als nächstes erstellen wir die Methoden zum korrekten Öffnen und Schließen der Datenbank. Öffne dazu wieder “DbConnect.h” und füge die beiden Methodendeklarationen ein. So sollte die Datei dann aussehen:
#import <Foundation/Foundation.h> #import "sqlite3.h" @interface DbConnect : NSObject -(void) openDb; -(void) closeDb; @end
Öffne dann “DbConnect.m” und füge die zwei Methoden unter den Instanzvariablen, nach der geschlossenen geschweiften Klammer ein:
-(void) openDb {
// Den Pfad zur Documents-Directory in path speichern
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
dbPath = [documentsDirectory stringByAppendingPathComponent:@"shoppinglist.sqlite3"];
NSFileManager *fileManager = [NSFileManager defaultManager];
// Die Datenbank aus dem Bundle in die Documents-Directory kopieren
NSString *pathInMainBundle = [[NSBundle mainBundle] pathForResource:@"shoppinglist" ofType:@"sqlite3"];
if (![fileManager fileExistsAtPath:dbPath]) {
NSLog(@"Datenabnk noch nicht vorhanden");
[fileManager copyItemAtPath:pathInMainBundle toPath:dbPath error:nil];
}
// Die Datenbank öffnen
int result = sqlite3_open([dbPath UTF8String], &database);
if (result != SQLITE_OK) {
sqlite3_close(database);
NSLog(@"Fehler beim Öffnen der Datenbank");
return;
}
NSLog(@"Datenbank erfolgreich geöffnet");
}
-(void) closeDb {
sqlite3_close(database);
NSLog(@"Datenbank erfolgreich geschlossen");
}
Zuerst zu openDb. Das ganze sieht ziemlich kompliziert aus, ist es aber gar nicht
. Allerdings geht es hier um das Thema “Dateien laden”, welches Teil des nächsten Tutorials werden wird. Trotzdem werde ich kurz erläutern was in dieser Methode geschieht.
Zuerst ermitteln wird den Pfad zur sogenannten Documents-Directory. In diesem Ordner werden Daten des User einer App gespeichert. Das heißt jede App, die der Nutzer auf sein iPhone lädt, besitzt so einen Ordner (auch das iPhone hat ein Dateisystem). In ihm werden dann solche Daten wie der Highscore bei einem Spiel oder eben eine Einkaufsliste-Datenbank gespeichert. Nach den ersten zwei Anweisungen (Zeile 3-4) ist in der Variable “documentsDirectory” der Pfad zu dieser Documents-Directory gespeichert. In Zeile 5 hängen wir an diesen Pfad noch den Namen unserer Datenbank dran. Damit ist der Pfad zu unserer Datenbank komplett.
Im nächsten Schritt (Zeile 7-13) kopieren wir die Datenbank aus dem Main-Bundle in diese Documents-Directory. Das Main-Bundle enthält bspw. Bilder, die man für einen UIImageView benötigt. Man fügt Dateien zum Main-Bundle hinzu, indem man sie in den Projekt-Ordner kopiert. Das haben wir vorhin auch mit unserer shoppinglist-Datenbank gemacht. Das Problem ist allerdings, das alle Dateien im Main-Bundle readonly sind. Das bedeutet, es wäre nicht möglich, neue Einkaufsgegenstände zur Liste hinzuzufügen, da die Datenbank nicht bearbeitet werden dürfte. Das ist der Grund, warum wir die Datenbank aus dem Main-Bundle heraus in die Documents-Directory kopieren.
Auch hier nur eine grobe Erläuterung. In Zeile 7 erstellen wir einen NSFileManager, der später zum Einsatz kommt. In Zeile 9 weisen wir der Membervariable dbPath, die wir weiter oben deklariert haben, den Pfad zu “shoppinglist.sqlite3″ im Main-Bundle zu. In Zeile 10 überprüfen wir mit Hilfe des FileManagers, ob die Datenbank bereits am vorbestimmten Pfad in der Documents-Directory ist. Wenn nicht kopieren wir sie mit Hilfe des Dateimanagers hinein (Zeile 12).
Jetzt kommt der eigentliche SQLite-Teil in dieser Methode. In Zeile 16 öffnen wir die Datenbank mit der C-Methode sqlite3_open. Anders als bei Objective-C-Methoden werden die Parameter in C in runden Klammern geschrieben und mit einem Komma voneinander getrennt. Ansonsten besitzt diese Methode keine für uns relevanten Unterschiede.
sqlite3_open hat einen Rückgabewert vom Typ int. Ein Rückgabewert einer Methode ist einfach gesagt, eine Variable, die uns die Methode übergibt, wenn sie fertig ausgeführt wurde. Zugegriffen wird auf diesen Rückgabewert, indem man mit der Methode verfährt, wie mit einer Variable. Das sehen wir auch gleich in dieser Zeile. Wir speichern nämlich den Rückgabewert in der Variable result. Dabei gehen wir so vor, als wäre die Methode einfach eine Variable, die wir result zuweisen. Der Sinn eines Rückgabewertes ist, dass man das Ergebnis einer Methode auch außerhalb von dieser verwenden kann. Bei der Methode sqlite3_open ist das Ergebnis nur, ob die Datenbank erfolgreich geöffnet wurde oder nicht. Und damit wir dies in unserem Code überprüfen können, wird das Ergebnis als Rückgabewert an uns übermittelt. Verwenden werden wir den in result gespeicherten Rückgabewert erst in der nächsten Zeile, um eben die Überprüfung durchzuführen.
Jetzt zu den Argumenten der Methode. Das erste Argument von sqlite3_open ist der Pfad zu unserer Datenbank “shoppinglist.sqlite3″. Das seltsame dabei ist, dass wir nicht einfach den NSString übergeben, sondern den Rückgabewert der Methode UTF8String von dbPath. Ganz am Anfang des Tutorials erwähnte ich, dass die sqlite3-Library in der Programmiersprache C geschrieben ist. Diese Programmiersprache weiß mit dem Datentyp NSString nichts anzufangen und deshalb müssen wir die Zeichenkette, also den Pfad, in einen UTF8String konvertieren. Praktisch ist, dass Apple dazu die Methode UTF8String zur Verfügung stellt, welche einfach den konvertierten String zurückgibt (das ist wieder ein Beispiel für einen Rückgabewert, diesmal bei einer Objective-C-Methode; das Ergebnis der Methode ist dabei der konvertierte String, den wir hier einfach als Argument einer anderen Methode verwenden).
Eine weitere Besonderheit tritt bei Argument 2 auf. Hier übergeben wir die Instanz unserer sqlite3-Datenbank, welche wir weiter oben im Tutorial erstellt haben. Allerdings steht vor der Variable das Zeichen &. Damit übergeben wir nicht die Instanz selbst, sondern lediglich eine Referenz. Wenn ich darauf nun genauer eingehen würde, fände das Tutorial gar kein Ende mehr, weshalb ich es hier mal dabei belasse, dass die Datenbank bei sqlite3_open mit dem &-Zeichen übergeben werden muss.
Weiter geht es in Zeile 17, wo wir mit der besagten Variable result arbeiten. Es wird die besagte Überprüfung durchgeführt, ob das Öffnen der Datenbank Erfolg hatte oder nicht. Dabei wird das Stichwort SQLITE_OK benutzt. Auf den ersten Blick sieht das so aus, als würden wir einfach zwei Variablen vergleichen. Doch ist SQLITE_OK keine Variable, sondern eine Konstante. Konstanten werden einmal deklariert und können ihren Wert dann nicht mehr ändern. Bleibt noch die Frage, wo diese Konstante definiert wurde. Dafür gibt es eine praktische Xcode-Funktion. Setze den Cursor auf SQLITE_OK und klicke mit der rechten Maustaste darauf. Wähle dann “Jump to Definition”.
Daraufhin wird im Editor eine andere Datei angezeigt, in der eine Zeile markiert wurde (Welche Datei das ist, siehst Du in der Leiste über dem Editor-Bereich –> “sqlite3.h”). Das ist genau die Datei, die wir weiter oben in “DbConnect.h” eingebunden haben.
#define SQLITE_OK 0 /* Successful result */
Hier findet also die Definition der Konstante statt, die allerdings etwas anders als gewohnt aussieht. Zuerst steht das #-Zeichen, wie es auch bei #import der Fall ist. Dann steht der Name der Konstante und mit einigem Abstand der Wert, den sie annehmen soll.
Und da wir eben die Datei “sqlite3.h” vorhin in der Header-Datei eingebunden haben, haben wir Zugriff auf diese Konstante.
Warum aber wird extra eine Konstante mit dem Wert 0 deklariert? Schließlich bewirkt unsere if-Bedingung in “DbConnect.m” damit nichts anderes als if(result != 0). Nun, das Ganze ist eine Vereinfachung für den Nutzer der sqlite-Library. Wie Du in sqlite3.h siehst, gibt es nämlich weit mehr Konstanten als nur SQLITE_OK. Und hinter jeder steckt eine Zahl. Ohne diese Konstanten müsste man immer die richtigen Zahlen parat haben, welche man sich nicht so leicht merken kann, wie einen durchaus aussagekräftigen Konstanten-Namen.
Übrigens steht auch gleich hinter der Definition der Konstante, was diese bedeutet. Nämlich dass beim Öffnen der Datenbank alles geklappt hat.
Einschub – Präprozessor Direktiven: Dir ist vielleicht schon aufgefallen, dass Zeilen in Xcode, die mit einem # beginnen, eine andere Färbung haben als normale Anweisungen. Das liegt daran, dass es sich bei diesen um sogenannte Präprozessor Direktiven handelt. “Präprozessor” deswegen, weil diese Direktiven ausgeführt werden, bevor das restliche Programm übersetzt wird. Das lässt sich mit der Konstante SQLITE_OK als Beispiel verdeutlichen: Vor dem Übersetzen des Programms setzt der Präprozessor einfach überall, wo das Stichwort “SQLITE_OK” steht, den Wert 0 ein. Das bedeutet, es macht wirklich keinen Unterschied, ob man schreibt if(result != SQLITE_OK) oder if(result != 0). Weitere Präprozessor Direktiven sind #import oder #warning (mit dieser Direktive kann man sich selbst oder einem anderen Programmierer eine Warnung schreiben
)
Zurück zu unserer Methode openDb. Um wieder die Datei DbConnect.m anzeigen zu lassen, klicke einfach auf diese Datei im Project navigator, auch wenn es so scheint, als wäre sie schon ausgewählt
. Wir waren stehengeblieben bei der if-Bedingung in Zeile 17. Wie gesagt wird hier überprüft, ob beim Erstellen der Datenbank alles gut gegangen ist. Ist dies nicht der Fall ( result != SQLITE_OK ), schließen wir die Datenbank sofort wieder mit sqlite3_close. Argument diese Methode ist wieder die Datenbank, allerdings diesmal nicht als Referenz, also ohne das &-Zeichen.
In Zeile 19 befindet sich eine NSLog-Anweisung. NSLog-Anweisungen sind für den Programmierer selbst eine Hilfe, um zu sehen, was das eigene Programm beim Ausführen gerade macht. Wenn wir die App später starten, erhalten wir an der Stelle, an der im Code eine NSLog-Anweisung steht, eine Ausgabe in der Konsole. Wie diese Ausgabe aussieht, zeigt das erste Argument, nämlich der Text der Ausgabe. Wo sich die Konsole befindet, zeige ich, wenn wir die fertige App das erste Mal ausführen.
Wenn dann etwas beim Ausführen der App dieser Text in der Konsole angezeigt wird, wissen wir, dass beim Öffnen der Datenbank ein Fehler aufgetreten ist, da der NSLog-Befehl nur ausgeführt wird, wenn result != SQLITE_OK zutrifft.
In Zeile 20 steht ein return. Mit return können wir selbst einen Rückgabewert unserer Methode festlegen. Wollten wir, dass unsere Methode openDb den Rückgabewert 1 hätte, wenn ein Fehler auftritt, würden wir schreiben return 1; . Das macht man aber nur, wenn der Rückgabewert dieser Methode in irgendeiner Weise relevant ist, das heißt, wenn wir an einer anderen Stelle im Code überprüfen würden, ob der Rückgabewert 1 oder 0, 2, 3, … ist (so wie wir es beim Rückgabewert von sqlite3_open gemacht haben). Das ist bei openDb nicht der Fall, weshalb wir einfach nichts zurückgeben.
Hinweis: Wäre der Rückgabewert allerdings nicht ‘nix’, sondern vom Typ int, NSString, float…, so müssten wir im Kopf der Methode, also bei der Zeile -(void) opendDb, nicht void in Klammern schreiben sondern int, NSString, float, … Der Rückgabewert einer Methode wird also immer vor dem Methoden-Namen in Klammern angegeben. Hat die Methode keinen Rückgabewert (also eben ‘nix’ als Rückgabewert), so gibt man void an.
Dieses return hat aber noch einen netten Nebeneffekt. Denn mit return …; wird eine Methode sofort beendet. Alles was nach einem return steht, wird einfach nicht mehr ausgeführt. Man könnte sagen, man bricht mit return die Ausführung einer Methode ab. Voraussetzung ist natürlich, dass das return wirklich ausgeführt wird, also in unserem Beispiel die if-Bedingung zutrifft. Ist das der Fall, so wird alles ab einschließlich Zeile 22 nicht mehr ausgeführt. Das macht auch nur Sinn, denn an dieser Stelle geben wir in der Konsole aus, dass beim Öffnen der Datenbank alles geklappt hat, was ja nur der Fall ist, wenn die if-Bedingung nicht eintritt.
So endlich mit openDb fertig.
closeDb ist jetzt auch nicht mehr schwer. Jede Datenbank sollte nach der Benutzung wieder ordnungsgemäß geschlossen werden, was wir in dieser Methode bewerkstelligen.
Hier wird lediglich die Methode sqlite3_close aufgerufen und die Datenbank übergeben. Dann geben wir mit NSLog aus, dass die Datenbank geschlossen wurde.
Damit haben wir jetzt schon zwei der wichtigsten Methode fertig. Als nächstes erstellen wir eine Methode, mit deren Hilfe wir ein Array mit allen Einkaufsgegenständen erhalten.
Öffne dazu zuerst “DbConnect.h” und füge die Methoden-Deklaration zu den anderen hinzu:
-(NSMutableArray *) getItems;
Schon am Methoden-Kopf kann man erkennen, dass diese Methode einen Rückgabewert vom Typ NSMutableArray* besitzen wird.
Gehe dann wieder zurück zu “DbConnect.m” und füge die Methoden Definition ein.
-(NSMutableArray *) getItems {
NSMutableArray *items = [[NSMutableArray alloc] init];
if (sqlite3_prepare_v2(database, "SELECT title FROM shoppinglist ORDER BY uniqueId", -1, &statement, nil) == SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
NSString *message = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 0)];
[items addObject:message];
}
sqlite3_finalize(statement);
}
return items;
}
In dieser Methode geht es um das Abfragen von Daten aus der SQLite-Datenbank.
In Zeile 2 erstellen wir das Array, indem wir im Laufe der Methode alle Einkaufsgegenstände, also items, aus der Datenbank speichern.
In Zeile 4 kommen wir zur ersten SQLite-Methode, sqlite3_prepare_v2. Mit dieser bereiten wir die Ausführung einer SQLite-Abfrage vor. Als erstes Argument übergeben wir dabei selbstverständlich die Datenbank, auf die sich die Abfrage bezieht.
Das zweite Argument ist der eigentliche Abfrage-Befehlstext. Auf die Abfrage an sich gehe ich erst später genauer ein. Es handelt sich bei dem String, der übergeben wird, wiedermal nicht um einen NSString, sondern um einen UTF8-String. Das erkennt man daran, dass kein @-Zeichen vor dem Anführungszeichen steht. Hätten wir diesen Abfrage-Befehl vorher in einem NSString zwischengespeichert, so müssten wir ihn wieder mit Hilfe der UTF8-Methode übergeben: [abfrageString UTF8String].
Das dritte Argumente ist ein Integer. Da dieses Argument bei den meisten Abfragen -1 ist, gehe ich nicht näher darauf ein.
Als viertes Argument übergeben wir der Methode unser am Anfang des Tutorials erstelltes Statement. Diese Variable benötigen wir später, um die Abfrage auszuführen.
Auch das letzte Argument der Methode ist meist einfach nil.
Jetzt zur eigentlichen Abfrage: “SELECT title FROM shoppinglist ORDER BY uniqueId”.
Sie besteht aus drei Keywords: SELECT, FROM und ORDER BY. Diese wurden der Übersicht halber in Großbuchstaben geschrieben. Mit SELECT wird ausgewählt, welche Spalten für das Ergebnis der Abfrage interessant sind. Unsere Tabelle besteht aus insgesamt vier Spalten: uniqueId, title, number und shop. Da unsere Methode getItem lautet, ist nur der Titel des Einkaufsgegenstandes von Bedeutung, also die Spalte title. FROM gibt an, aus welcher Tabelle abgefragt werden soll. Da eine Datenbank auch mehrere Tabellen enthalten kann, wird dieses Statement für die Abfrage benötig, bei uns ist es deshalb einfach “shoppinglist”. Mit ORDER BY legen wir fest, in welcher Reihenfolge das Ergebnis der Abfrage vorliegen soll, also welche Einkaufsgegenstände am Anfang stehen und welche am Ende. Es ist sinnvoll hier nach uniqueId zu sortieren, da so die Reihenfolge, in der die Einkaufsgegenstände hinzugefügt wurden, erhalten bleibt.
Die Methode befindet sich in einem if-Statement, mit dem wieder überprüft wird, ob beim Vorbereiten der Abfrage alles geklappt hat. Dabei wird wieder die Konstante SQLITE_OK verwendet. Nur wenn die Bedingung zutrifft, werden die folgenden Zeile ausgeführt.
In Zeile 6 – 9 ist eine while-Schleife zu finden. Mit Hilfe einer Schleife, kann ein bestimmter Code-Ausschnitt beliebig oft ausgeführt werden. Begrenzt wird die Anzahl der Durchgänge durch die Bedingung, die hinter dem Keyword while in Klammern steht. Solange diese Bedingung also zutrifft, wird der Code innerhalb der geschweiften Klammern immer und immer wieder ausgeführt. Sobald die Bedingung aber nicht mehr zutrifft, springt der Compiler hinter die geschlossene geschweifte Klammer und führt den nachfolgenden Code aus.
Die Bedingung enthält in unserem Fall wieder eine SQLite3-Methode: sqlite3_step. Mit dieser Methode wird die Abfrage, die wir mit sqlite3_prepare_v2 vorbereitet haben, schließlich ausgeführt. Dabei übergeben wir ihr das Statement, das wir auch schon der Methode sqlite_preapre_v2 übergeben haben. In dieser Variable wurde die fertig vorbereitete Abfrage gespeichert, die wir jetzt ausführen können.
Dabei wird bei jedem Durchgang überprüft, ob der Rückgabewert von sqlite3_step immer gleich SQLITE_ROW ist. SQLITE_ROW ist genauso wie SQLITE_OK eine Konstante und steht für einen bestimmten Zahlenwert, der für uns aber nicht von Belang ist (wer ihn trotzdem wissen will, kann dies wieder mit Jump to Definition herausfinden). Der Rückgabewert von sqlite3_step ist solange SQLITE_ROW, solange passende Ergebnisse in der Tabelle der Datenbank (in unserem Fall “shoppinglist”) gefunden werden. “Nicht passende” Tabellen-Einträge gibt es nur dann, wenn die Abfrage eine Bedingung enthält bspsw.: “SELECT title FROM shopinglist WHERE number = 3 ORDER BY uniqueId”. Alle Einkaufsgegenstände, von denen nicht 3 gekauft werden müssen, würden bei der Abfrage ignoriert werden und nicht als Ergebnis aufgeführt werden. Da dies bei uns aber nicht der Fall ist, ist jede der drei Zeilen der Tabelle ein Ergebnis der Abfrage.
Wichtig für das Verständnis ist, dass der Abfragevorgang nach jedem gefundenem Ergebnis praktisch pausiert ist. Deshalb verwenden wir auch eine while-Schleife.
Das sieht so aus: sqlite_step ausführen –> passendes Ergebnis gefunden –> Code zwischen den geschweiften Klammern der while-Schleife ausführen (zu diesem komme ich gleich ) –> wieder sqlite_step .
Ist die Abfrage dann am Ende der Tabelle angekommen, kann also kein Ergebnis mehr geliefert werden, so ist der Rückgabewert von sqlite3_step nicht mehr SQLITE_ROW, die Bedingung der while-Schleife wird damit nicht mehr erfüllt, also wird diese beendet.
Einschub: sqlite3_step liefert wirklich nur dann nicht mehr den Wert SQLITE_ROW, wenn die Abfrage am Ende der Tabelle angekommen ist. Kommt aber während des Abfragens mal ein “nicht passender” Tabelleneintrag, so wird dieser, noch während sqlite_step ausgeführt wird, einfach übersprungen und der nächste Tabelleneintrag überprüft. Ist dieser dann wieder ein Ergebnis der Abfrage, so würde die Methode ganz normal SQLITE_ROW als Rückgabewert liefern.
Weiter geht es in Zeile 7. Hier erstellen wir einen NSString namens message. Um ihn zu initialisieren, verwenden wir die Methode [NSString stringWithUTF8String: ]. Eine Besonderheit ist hier, dass nicht eine Instanz die Methode stringWithUTF8String aufruft, sondern der Typ NSString. Das heißt einfach, dass die Methode nicht von einer Instanz einer Klasse aufgerufen wird, sondern von der Klasse selbst. In diesem Fall bewirkt die Methode, dass der UTF8-String (C-String), der als Argument übergeben wird, in einen NSString umgewandelt wird und in message gespeichert wird.
Jetzt kommen wir zu eben diesem UTF8-String, der übergeben wird. Dabei lassen wir das “(char *)” vorerst unbeachtet und konzentrieren uns auf den Teil danach, nämlich “sqlite3_column_text(statement, 0)”.
Es handelt sich hierbei wieder um eine SQLite-Methode, der wir als erstes Argument erneut die Variable statement übergeben. Was diese Methode bewirkt, sollte jetzt nicht mehr schwer zu erraten sein: Beim Überprüfen der while-Bedingung haben wir die Methode sqlite3_step aufgerufen, die nach einem Ergebnis der Abfrage in der Tabelle sucht. Diese Suche war anscheinend erfolgreich, sonst wäre nämlich Zeile 7 gar nicht aufgerufen worden. Und da sqlite3_step eben erfolgreich war, wird die Position des Ergebnisses in der Tabelle in statement gespeichert. Genau diese Information verwenden wir jetzt in sqlite3_column_text. Diese Methode ist nur noch dazu da, die Informationen des Ergebnisses zu extrahieren, also die Information der besagten Zelle auszulesen.
Es gibt noch weitere Methode nach dem Muster sqlite3_column_<type>, so beispielsweise sqlite3_column_int, wenn man einen Zahlenwert, wie beispielsweise uniqueId extrahieren möchte.
Zu erwähnen ist noch das zweite Argument der Methode sqlite3_column_<type>. Dazu muss man aber wissen, dass sich eine Abfrage nicht immer nur auf eine Spalte beziehen muss. Das heißt, wir könnten in einer Abfrage nicht nur die Spalte title abfragen, sondern noch beliebig viele weitere, wie number, shop, etc. Ein solche Abfrage würde wie folgt aussehen: “SELECT title, number, shop FROM shoppinglist ORDER BY uniqueId” oder wenn man gleich alle Spalten einbeziehen möchte einfach “SELECT * FROM shoppinglist…”. Da man aber immer nur eine Spalte einer Zeile auf einmal extrahieren kann, muss man mit Hilfe des zweiten Arguments angeben, welche das sein soll. Hierbei wird wie in der Informatik üblich gezählt, nämlich 0, 1, 2 etc.
Jetzt kommen wir zu dem mysteriösen “(char*)”. So mysteriös ist das gar nicht, denn es kam schon im iPad-Tutorial vor. Es handelt sich um einen sogenannten Type Cast. Ein Type Cast ist, wenn man den Typ einer Variable in einen anderen Typ umwandelt. Soll heißen, wenn man eine int-Variable, aus welchem Grund auch immer, gerne als float benutzen möchte, castet man sie um. Das würde so aussehen:
int myInt = 5;
float division = (float)myInt / 3;
Man hat also einen Integer mit dem Wert 5. Jetzt möchte man diesen Wert, myInt, durch 3 teilen und das Ergebnis in division speichern. Bevor wir die Division durchführen, wandeln wir myInt in eine float-Variable um. Das Ergebnis ist dann 1.666… Lässt man dieses (float) aber weg, so würden die Nachkommastellen einfach abgeschnitten werden und das Ergebnis wäre 1 (da wir das Ergebnis dann aber in einer float-Variable speichern wird es automatisch zu 1.00000).
Gecastet wird also immer mit der Syntax (neuer_Datentyp)Variable_mit_alten_Datentyp .
Zurück zu unserem Code. Hier wird der Rückgabewert der Methode sqlite3_column_text in einen char* gecastet. Interessant zu wissen wäre, von welchem Typ aus gecastet wird. Da kommt wieder das magische “Jump to Definition”-Feature von Xcode ins Spiel. Setze den Cursor dazu auf die Methode sqlite3_column_text, klicke mit der rechten Maustaste darauf und wähle “Jump to Definition”. Die gewünschte Zeile ist wieder markiert. Der Rückgabewert der Methode ist zwar nicht wie üblich in Klammern angegeben, aber man erkennt ihn an seiner unterschiedlichen Färbung.

(Bitte sich nicht von meinem unterschiedlichen Xcode-Editor-Style verwirren lassen
).
Der Rückgabewert ist vom Typ “const unsigned char *”. Er setzt sich also eigentlich aus drei Teilen zusammen.
Mit const kann man eine Konstante erstellen (das heißt #define ist nicht die einzige Möglichkeit Konstanten zu erstellen). Das nächste Keyword ist unsigned, welches man als Modifizierer bezeichnet. Um seine Funktion zu verstehen braucht es ein wenig Hintergrundwissen:
Wenn wir bspw. eine int-Variable erstellen, so benötigt diese eine bestimmte Menge Speicherplatz. Pro ein Byte verwendeten Speicherplatz, kann die Variable 256 verschiedene Werte aufnehmen. Da man in einem Integer standardmäßig auch negative ganze Zahlen speichern kann, liegt der Wertebereich also bei -127 – 128 (eingeschlossen der 0). Wenn man nun allerdings eine Variable deklarieren möchte, die man nur für positive Zahlen verwenden will, so ist das unvorteilhaft, da man praktisch die Hälfte des Wertebereichs verliert. Dafür gibt es den Modifizierer unsigned. Wenn man bei der Variablen-Deklaration dieses Keyword davorsetzt, wird festgelegt, dass der Wertebereich von 0 bis 255 geht. Das gegensätzliche Keyword, also das, das auch negative Zahlen mit einbezieht, lautet übrigens signed, allerdings ist dieses bei der normalen Deklaration einer Variable standardmäßig gesetzt. Hier noch ein Beispiel, um das Gesagte zu verdeutlichen:
int signedInteger1; // -127 bis 128
signed int signedInteger2; // -127 bis 128
unsigned int unsignedInteger; // 0 bis 255
Dies sollte man im Hinterkopf behalten, wenn wir das Ganze jetzt auf char-Variablen anwenden.
Eine char-Variable stellt ja bekanntlich ein Zeichen dar (char kann nur ein einzelnes Zeichen aufnehmen, char* kann Zeichenketten aufnehmen). Das heißt man kann Folgendes machen:
char myChar = 'a';
Wir weisen der Variable einfach den Buchstaben a zu. Wichtig ist, dass man, wenn man einem char nur das eine Zeichen zuweist, man statt der Anführungsstriche Apostrophe verwendet.
Man kann einem char aber nicht nur Buchstaben zuweisen. Denn nach dem ASCII-Standard gibt es für jedes Zeichen auch eine Zahl, die dieses praktisch repräsentiert. Eine Übersicht über diese Zeichen mit den dazugehörigen Zahlen findest Du bspw. hier: http://www.asciitable.com/.
Der Tabelle auf dieser Seite können wir entnehmen, dass der Buchstabe ‘a’ durch die Dezimalzahl (Dec) 97 repräsentiert wird. Das bedeutet in diesem Code:
char myChar = 97;
würde die Variable myChar genauso wieder den Buchstaben ‘a’ als Wert besitzen.
Wie vorhin unsere int-Variable besitzt auch unsere char Variable, wenn nicht anders angegeben einen Wertebereich von -127 bis 128. Da negative Zahlen laut der Tabelle allerdings keine Zeichen vertreten, ist dieser Wertebereich eher unvorteilhaft, da alle Zeichen von 129-255 fehlen. Verwendet man aber einen unsigned char, also einen char mit Wertebereich von 0 bis 255, so lassen sich auch die Zeichen anzeigen, die auf der genannten Website unter Extended ASCII Codes gelistet sind.
Der Grund für die Verwendung von unsigned-Char ist also, dass einem ein größeres Zeichen-Spektrum zur Verfügung steht.
Um wieder zu unserem ursprünglichen Code zurückzukommen: Mit (char *) sqlite3_column_text(statement,0) wandeln wir den Typ des Rückgabewerts der Methode von const unsigned char * in char * um, denn nur so können wir ihn mit Hilfe der Methode stringWithUTF8String in der NSString-Variable message speichern.
In Zeile 8 speichern wir dann den in Zeile 7 extrahierten Inhalt der Tabellen-Zelle in items, dem Array, das wir am Anfang der Methode getItems deklariert haben. Wie bereits erwähnt enthält dieses Array, nachdem die while-Schleife zu Ende ist, alle Einkaufsgegenstände aus der Tabelle, sortiert nach ihrem uniqueId.
In Zeile 11 ist noch ein letzter SQLite-Befehl zu finden, nämlich sqlite3_finalize. Als Argument übergeben wir hierbei erneut die Abfrage-Variable statement. Die Methode löscht eine vorher mit sqlite3_prepare_v2 vorbereitet Abfrage. Dies ist sinnvoll, weil wir ja mit unserer Abfrage fertig sind und das items-Array vollständig ist. Da wir zudem die Variable statement theoretisch in einer anderen Abfrage verwenden wollen, sollte die alte Abfrage vorher entfernt werden.
Zum Schluss wird noch in Zeile 14 der Rückgabewert unserer Methode getItems festgelegt. Man gibt ihn einfach mit return zurück. In unserem Fall ist das das Array items. Um auf das am Anfang des Tutorials Erwähnte zurückzukommen: Das Array items ist das Ergebnis unserer Methode. Da wir dieses Ergebnis später auch außerhalb der Methode benötigen werden, geben wir es mit return zurück.
Somit ist auch diese Methode zu Ende und tatsächlich sind wir mit unserer App auch fast fertig.
6. Anwendung der Klasse DbConnect
Wir gehen nun über zur praktischen Anwendung unserer eben erstellten Klasse. Wir haben nämlich immer noch einen Table View zu implementieren. Öffne dazu die Datei “ShoppinglistViewController.m”.
Zuerst binden wir unsere Klasse ein. Schreibe dazu einfach folgenden Code unter “#import “ShoppinglistView…”:
#import "DbConnect.h"
Wie immer erstellen wir noch ein Array, das den Inhalt der Zeilen des Table Views enthält. Außerdem benötigen wir auch noch eine Instanz unserer Klasse DbConnect, damit wir dann mit der Datenbank arbeiten können. Alle beide Properties werden wieder in der .m-Datei deklariert. Ändere also den Code, der direkt unter den zwei #import-Zeilen steht:
@interface ShoppinglistViewController () @property (nonatomic, strong) NSMutableArray *itemsArray; @property (nonatomic, strong) DbConnect *dbConnect; @end
Synthetisiere die beiden Properties dann unter der Zeile “@implementation ShoppinglistView…”:
@synthesize itemsArray; @synthesize dbConnect;
Jetzt kommt die Instanzvariable dbConnect auch gleich zum Einsatz, nämlich in viewDidLoad. Ersetze diese Methode also durch Folgendes:
- (void)viewDidLoad
{
[super viewDidLoad];
dbConnect = [[DbConnect alloc] init];
[dbConnect openDb];
itemsArray = [dbConnect getItems];
[dbConnect closeDb];
}
Eigentlich gibt es hier nicht mehr viel zu erklären. Wir gehen hier in der einzig logischen Reihenfolge vor. Erst öffnen wir die Datenbank mit openDb, dann fragen wir mit getItems alle Einkaufsgegenstände ab und schließen die Datenbank dann wieder. Übrigens wird in Zeile 7 der Rückgabewert von getItems, also das Array mit den Items, im itemsArray gespeichert.
Übrig bleiben jetzt nur noch die Standrad-Arbeiten bei einem Table View. Ändere die drei Methoden ab:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [itemsArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"ItemCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
cell.textLabel.text = [itemsArray objectAtIndex:indexPath.row];
return cell;
}
In der obersten Methode wird die Anzahl der Sections festgelegt, in der nächsten die Anzahl der Zeilen. Die ist gleich der Anzahl der Elemente des itemsArray, welches alle Einkaufsgegenstände enthält. In tableView:cellForRowAtIndexPath: ändern wir nur den CellIdentifier auf den Wert, den wir unserer Tabellen-Zeile im Storyboard gegeben haben. Dann erhält noch unter “// Configure the cell…” jede Tabellenzeile den Titel eines Einkaufsgegenstandes.
Jetzt ist die App endgültig fertig. Führe sie mit einem Klick auf Run aus. Es sollte einfach die Tabelle mit den drei Einkaufsgegenständen angezeigt werden.
Kehre nun, noch während die App ausgeführt wird, zum Xcode-Fenster zurück. Unter dem Editor-Bereich sollte sich ein kleines Fenster eingeschoben haben:
Das ist die sogenannte Konsole. Am Anfang des Tutorials habe ich erläutert, das NSLog-Anweisungen ihren Text dort ausgeben. Mit deren Hilfe können wir nun verfolgen, was die App bisher gemacht hat.
Zuerst wird der Text ausgegeben, dass die Datenbank noch nicht in der Documents Directory vorhanden ist. Dann wurde die Datenabnk erfolgreich geöffnet und dann auch wieder geschlossen. Anhand dieser Zeilen können wir feststellen, dass die App so funktioniert, wie sie soll. Träte nämlich ein Datenbank-Fehler auf, so würde in der Konsole der Text “Fehler beim Öffnen der Datenbank” angezeigt werden, den wir in der Methode openDb festgelegt haben.
Wenn Du die App nun erneut ausführst, so wird “Datenbank noch nicht vorhanden” nicht mehr angezeigt, da sie zuvor schon in die Documents Directory kopiert wurde.
7. Schluss
Den Code gibt es natürlich zum Downloaden: SQLiteTutorial
Geschafft!
Wenn Du das Tutorial ganz durchgearbeitet hast, kannst Du wirklich stolz auf dich sein. Ich hoffe es war ausführlich genug erklärt und ich habe alles verständlich rübergebracht. Wenn nicht steht die Kommentarfunktion wie immer für eure Fragen und Verbesserungsvorschläge bereit.
Außerdem würde mich interessieren, ob ihr euch noch eine Fortsetzung des Tutorials wünscht, in dem ich bspw. erläutere, wie man innerhalb der App neue Einkaufsgegenstände hinzufügt. Oder doch lieber ein Core Data Tutorial…
Wenn ihr noch andere Vorschläge habt, könnt ihr mir diese selbstverständlich auch gerne zukommen lassen.
Wenn Du immer auf dem Laufendem gehalten werden möchtest, kannst Du mir gerne auf Twitter folgen oder den Blog RSS abonnieren.










Im openDb hat sich in Zeile 9 ein kleiner Fehler eingeschlichen:
NSString *pathInMainBundle = [[NSBundle mainBundle] pathForResource:@"shoppinglist"; ofType:@"sqlite3"];Ein Semikolon zu viel. So wäre es richtig.
NSString *pathInMainBundle = [[NSBundle mainBundle] pathForResource:@"shoppinglist" ofType:@"sqlite3"];Ansonsten: TOP-Tutorial!
Grüße
Bernd
Hallo Bernd,
vielen Dank für den Hinweis, das ist mir bisher nie aufgefallen.
Ich habe es gleich ausgebessert.
Viele Grüße
Felix
Hallo Felix,
bin noch Anfängerin …
Dein Tutorial hat mir wirklich sehr weiter geholfen. Echt super!!!
Das einzige was mir fehlt ist, wie kann ich denn Daten an einen DetailViewController weiter geben kann, also wenn ich weitere Infos in einem UIViewController anzeigen lassen will.
Habe das ganze bisher immer mit einer Property List gemacht, möchte nun aber SQLite nutzen.
Vielleicht kannst du das Tutorial ja weiter führen oder mir einen Tipp geben.
Im voraus vielen Dank
und viele Grüße Betti
Sorry, meinte natürlich “Hallo Felix”
Kein Problem, ich hab’s oben ausgebessert
Hallo Betti,
ich glaube was Du suchst ist Folgendes:
In diesem Tutorial wird erklärt, wie man Daten aus einem Table View Controller an einen Detail View Controller weitergibt. Das Ganze funktioniert natürlich auch kombiniert mit SQLite.
Viele Grüße
Felix
Ich bedanke mich auch für das super Tutorial. Wirklich sehr gut geschrieben.
Leider gibt es kaum iOS-Tutorials mit dieser Qualität.
Weiter so!
Vielen Dank für das Tutorial! Du hast Dir wirklich sehr viel Mühe gegeben.
Ein CoreData-Tutorial würde ich ebenfalls sehr begrüssen
Viele Grüße
Flo
Wunderbarer und unglaublich hilfreicher Artikel. Freu mich schon auf morgen wenn ich das ausgiebige testen kann. Arbeite mich gerade in Core Data ein, da kommt der SQLite Artikel hier genau richtig.
Vielen herzlichen Dank für die Mühe
Wunderbares Tutorial. Lieben Dank dafür.
Ein CoreData-Tutorial wäre meines Erachtens vonnöten. Da gibt es derzeit nicht wirklich überzeugendes. Selbst Ray Wenderlichs Tutorial ist wenig brauchbar. Gute Ansätze gab es im Koller-Buch, was aber in einem schlecht erklärten Durcheinander geendet ist.
Danke bis dahin
Andreas
Hallo Andreas,
danke für das Lob
Ich denke auch, dass ein Core Data Tutorial für viele Leser interessant sein könnte, gerade wenn, wie Du sagst, das Angebot nicht sehr gut ist. Vermutlich werde ich es bald mal außerhalb der Tutorials von der Tutorial-Umfrage schreiben, so als “Fortsetzung” von diesem Tutorial.
Viele Grüße
Felix