Swift Tutorial: iOS App
Um dieses Tutorial durchzuarbeiten benötigst Du keine Kenntnisse über XCode oder Swift. Alle notwendigen Schritte werden im Tutorial beschrieben
Während dieses Tutorials wirst Du lernen, wie Du einen TableView erzeugst, diesen mit Bildern und Texten füllst, dich zwischen verschiedenen Views bewegst und dabei Daten zwischen dein einzelnen ViewControllern bewegst.
Das vollständige Projekt findest Du auf GitHub
Abbildung 1:
Vorraussetzungen
Bevor Du mit dem Tutorial beginnen kannst benötigst du XCode 6 Beta 4 oder neuer. Zugang zu dieser Version von XCode erhälst Du, durch eine Mitgliedschaft im Apple Developer Program und natürlich benötigst du einen Mac.
Projekt Anlegen
Starte XCode und wähle die Option Create new Xcode Project. Wird dieser Bildschirm(Abbildung 2) nicht angezeigt kannst Du unter File>New>Project ein neues Projekt anlegen.
Abbildung 2:
Entscheide dich im folgenden Screen für eine iOS-SingleView Application.
Abbildung 3:
Gib dem Projekt den Namen Fotoliste, wähle die Sprache Swift und vergewissere Dich, dass Du eine iPhone App erstellst. Bestätige deine Eingaben mit Next.
Abbildung 4:
Gib einen Speicherort für Dein neues Projekt an, z.B. Dein Documents Verzeichnis.
Erster Start der App
Drücke auf das große Dreieck oder starte die App über den Menüpunkt Product/Run.
Abbildung 5:
Vorbereitungen für den ersten Testlauf
XCode erzeugt alles Nötige für einen ersten Start. Da ein weißer Bildschirm aber nicht besonders spannend ist, wirst Du ein Label auf dem Bildschirm platzieren und seinen Text anpassen. Klicke dazu in der Liste links auf Main.storyboard.
Abbildung 6:
Die Form des Views erinnert nicht gerade an ein iPhone, dies liegt daran dass XCode standardmäßig size classes verwendet. Das ist in diesem Tutorial aber nicht geplant, da dies ohne AutoLayout nicht sinnvoll verwendbar ist. Schalte die Funktion ab indem den View einmal anklickst und nun über das gelbe Symbol am oberen Rand des Views den kompletten View selektierst. Jetzt kannst Du den FileInspector öffnen (blaues Symbol Abbildung7) und deaktivierst darin die Option "Use Size Classes".
Abbildung 7:
Jetzt kannst Du per Drag&Drop aus dem Baukasten unten rechts ein Label zum View hinzufügen. Ändere den Labeltext zu "Hello World!" indem Du das Label durch einen Doppelklick in den Bearbeitungsmodus versetzt und den neuen Inhalt einsetzt.
Abbildung 8:
Klicke auf den iPhonebutton und wähle das aktuellste iPhone-Modell als Simulator.
Abbildung 9:
Über das Playsymbol oben links kannst Du die App auf dem Simulator starten. Nach einer kurzen Startuptime siehst du dein Label im Simulator.
Abbildung 10:
Erstellen eines TableViews
Das zu Testzwecken eingefügte Label kannst Du jetzt löschen. Selektiere es dazu mit einem Klick und drücke die Tastenkombination fn + backspace. An seiner Stelle füge jetzt einen TableView aus dem Baukasten ein. Achte darauf, dass der TableView den kompletten View bedeckt.
Abbildung 11:
Um einen Button in die Navigationsbar einbauen zu können, musst Du zuerst eine Navigationsbar hinzufügen. Dazu klickst Du auf das gelbe Symbol am oberen Rand des Views, damit selektierst du den kompletten View. Anschließend kannst über das Menü Editor>Embed In>Navigation Controller einen NavigationController zu Deinem View hinzufügen. Der NavigationController wird als neuer View auf dem Storyboard angezeiget. Die Verbindung der beiden Komponenten wird durch eine Relationship signalisiert. Ziehe den View neben den neuen NavigationController.
Abbildung 12:
Dein View verfügt jetzt über eine Navigationsbar, erweitere diese um ein Bar Button Item. Ziehe dazu ein Bar Button Item aus dem Baukasten in die Navigationsbar Deines Views.
Abbildung 13:
Das neue Bar Button Item wählst Du durch einen Klick aus, öffnest den AttributeInspector (blaues Symbol siehe Abbildung 14) und stellst darin den Identifier auf Add um. Durch diese Anpassung erscheint dein Button als ein +.
Abbildung 14:
Den Titel deines Views änderst Du indem Du doppelt auf die Mitte der Navigationsbar klickst und den Namen FotoListe vergibst.
Abbildung 15:
Bis jetzt hast Du lediglich das Aussehen des Views verändert, die Funktionen werden aber in der Swift-Klasse(dem Controller) zu diesem View definiert. Eine Verbindung zwischen View und Controller erzeugst Du, indem den kompletten View selektierst und dabei den IdentityInspector, durch einen Klick auf das in Abbildung 16 blau hervorgehobene Symbol öffnest. Darin setzt du als Class die Klasse ViewController.
Abbildung 16:
Mit weiterhin selektiertem View öffnest Du den Assistant Editor (blaues Symbol siehe Abbildung 17) Hat das Verbinden von Controller und View funktioniert, wird in der rechten Spalte die Klasse ViewController angezeigt.
Abbildung 17:
Erstelle ein Outlet des TablesViews im Code der Klase ViewController. Ein Outlet ist die Repräsentation eines UIElements im Code. Dieses Outlet erzeugst Du, indem du den TableView durch einen Klick auswählst, erneut auf ihn klickst und dabei die ctrl-Taste gedrückt hälst. Die Maus bewegst Du nun auf den Assistant-Editor, dabei entsteht ein blauer Pfeil. Diesen Pfeil bewegst du in die erste Zeile der Klasse. Lasse die linke Maustaste los und XCode erstellt ein Outlet für den TableView.
Abbildung 18:
Als nächstes gibst du dem TableView Outlet in dem sich öffnenden Dialog den Namen fotoListe.
Abbildung 19:
Der TableView besitzt nun eine Verbindung zum Code, er weiss aber noch nicht woher er seine Daten bekommen wird, oder wie diese strukturiert sind. Diese Information vermittelst Du, indem du den TableView erneut auswählst und mit Kombination aus linker Maustaste und CTRL einen Pfeil auf das gelbe Symbol am oberen Rand des Views ziehst. Es öffnet sich ein schwarzes Fenster mit den Einträgen delegate und dataSource. Wähle dataSource aus, und wiederhole dies für die Option delegate.
Abbildung 20:
Durch die Verlinkung zwischen View und Controller erwartet der TableView dass der Controller in der Lage ist die Views für die einzelnen Zeilen bereitzustellen. Dies setzt vorraus, dass der Controller die beiden Protokolle UITableViewDelegate und UITableViewDataSource implementiert. Füge diese Protkolle hinzu.
Abbildung 21:
Das Fehlen der Implementation wird XCode durch einen Compilerfehler signalisieren. Ergänze daher die folgenden zwei Methoden.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return UITableViewCell()
}
Test des TableViews
Die Methode didReceiveMemoryWarning kannst Du entfernen. Deine Daten für den TableView organisierst du in einer Liste. Diese Liste wird eine Instanzvariable der Klasse ViewController sein und den Namen bilderUndNotizen tragen. Erstelle diese nun innerhalb der Klasse ViewController.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
@IBOutlet weak var fotoListe: UITableView!
var bilderUndNotiz = [String]()
override func viewDidLoad() {
super.viewDidLoad()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return UITableViewCell()
}
}
Da eine leere Liste wenig Interessant ist, füllst Du die Liste mit initialen Daten innerhalb der viewDidLoad Methode.
override func viewDidLoad() {
super.viewDidLoad()
bilderUndNotiz += ["Bonn"]
bilderUndNotiz += ["Köln"]
bilderUndNotiz += ["Rom"]
bilderUndNotiz += ["Amsterdam"]
}
Gebe die Information über die Anzahl der Daten an den TableView weiter, sodass dieser weiß wie viele Zellen er darstellen muss.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return bilderUndNotiz.count
}
Es fehlt die Zuordnung von Information und UIElement. Da das Modell aus komplexen Daten bestehen kann, erfolgt die Zuordnung von Daten und UIElement manuell. Zusätzlich musst Du dafür sorgen, dass entweder eine neue Zelle erzeugt wird, oder aber eine vorhandene nicht weiter genutze Zelle, wieder verwendet wird.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let einfacherZellenIdentifier = "Ein Item"
var cell:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier(einfacherZellenIdentifier) as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: .Default, reuseIdentifier: einfacherZellenIdentifier)
}
cell!.textLabel?.text = self.bilderUndNotiz[indexPath.row]
return cell!
}
Starte die App. Der TableView zeigt Dir Deine Testdaten an.
Abbildung 22:
Erweitern des Datenmodells
Da Du mehr darstellen möchtest als eine einfache Liste mit Texten, musst Du Dein Datenmodell erweitern. Eine effektive Organisation der Daten wird durch eine zusätzliche Klasse BildUndNotiz emöglicht, die in der Lage ist ein Bild und Notiz zu speichern. Eine neue Klasse legst Du an, indem du einen Rechtsklick auf den Ordner Fotoliste machst und newFile auswählst. Im erscheinenden Dialog wählst du Cocoa Touch Class aus.
Die neue Klasse nennst Du BildUndNotiz, sie ist eine Unterklasse von NSObject und wird in der Sprache Swift geschrieben. Der Bildschirm sollte also so aussehen:
Abbildung 23:
Die neue Klasse kannst du im Root-Verzeichenis des Projekts speichern.
Erweitere diese Klasse um die beiden Felder bild als UIImage, text als String und einen Initializer.
import UIKit
class BildUndNotiz: NSObject {
var bild:UIImage
var text:String
init(bild: UIImage, text: String) {
self.bild = bild
self.text = text
}
}
Die initialen Daten kannst Du zusammen mit der Methode viewDidLoad entfernen, da diese nur zu Testzwecken dienten. Jetzt musst Du den Controller deines Einstiegsviews überarbeiten, so dass er Dein neues Datenmodell verwendet. Zuerst änderst du dazu den Datentyp der Liste auf [BildUndNotiz]. Nach dieser Änderung wird XCode neue Fehler melden, um welche Du dich nach und nach kümmern wirst.
var bilderUndNotiz = [BildUndNotiz]()
Durch Änderung Deines Datenmodells bedarf es Anpassungen bei der Zuordnung von Daten und UIElement. Gleiche daher die letzten beiden Zeilen der cellForRowAtIndexPath Methode entsprechend der Abbildung an.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let einfacherZellenIdentifier = "Ein Item"
var cell:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier(einfacherZellenIdentifier) as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: .Default, reuseIdentifier: einfacherZellenIdentifier)
}
cell!.textLabel?.text = self.bilderUndNotiz[indexPath.row].text
cell!.imageView?.image = self.bilderUndNotiz[indexPath.row].bild
return cell!
}
Ein Hinzufügen neuer Bilder ist aktuell noch nicht möglich, daher geben wir dem +-Knopf eine Funktion. Dieser Knopf wird einen UIImagePickerController erzeugen in dem du ein Bild aus deiner Fotosammlung auswählen kannst. Ergänze daher die Klasse ViewController um die Methode knopfDruck. Nachdem Du die Methode hinzugefügt hast, musst Du Methode und Button verbinden. Wähle dazu das Bar Button Item aus, erneut einen links Klick auf das element, diesen festhalten und zusätzlich CTRL drücken, den enstehenden Pfeil ziehst du auf die Methode knopfDruck.
@IBAction func knopfdruck(sender : AnyObject) {
var picker = UIImagePickerController()
picker.delegate = self
picker.allowsEditing = true
picker.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
self.presentViewController(picker, animated: true, completion: nil)
}
XCode warnt vor einem Fehler, da Du Deinen ViewController als delegate für den UIImagePickerController einsetzt, ViewController die benötigten Protkolle dazu aber nicht implementiert. Du ergänzt also die beiden Protkolle UIImagePickerControllerDelegate und UINavigationControllerDelegate zur Klasse ViewController.
Implementiere das Protokoll wie folgt:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
let chosenImage = info[UIImagePickerControllerEditedImage] as UIImage
let neuesBild = BildUndNotiz(bild: chosenImage, text: "")
bilderUndNotiz += [neuesBild]
self.fotoListe.reloadData()
picker.dismissViewControllerAnimated(true, completion:nil)
}
Nachdem die Implementation abgeschlossen ist, startest Du die App. Die Liste noch leer, Du kannst aber über das +-Symbol ein neues Bild hinzufügen. Bei dem Versuch auf die Bilder des Gerätes zuzugreifen, wird die App um Erlaubnis bitten dies zu tun. Bestätige diese Anfrage und es stehen die auf dem Gerät befindlichen Bilder zur Auswahl.
Abbildung 24:
Titelvergabe für Bilder
Zurück ans Zeichenbrett, Du brauchst einen weiteren Controller. Dazu ziehst Du einen ViewController aus dem Baukasten direkt neben den bereits vorhandenen ViewController.
Nun ziehst Du nach und nach zuerst einen ImageView, TextField sowie 2 Buttons aus dem Baukasten auf den neuen View. Die genaue Anordnung spielt keine Rolle, lediglich das TextFeld sollte in der oberen Hälfte liegen. Ohne die neuen Elemente zu verändern sollte das so aussehen:
Abbildung 25:
Jetzt kannst du die Größen der UI-Komponenten an die zur Verfügung stehende Größe des Bildschirms anpassen. Die Größe einer Komponente lässt sich durch Selektieren und Ziehen der kleinen Quadrate, wie ein Betriebssystem-Fenster, in der Größe editieren. Die Texte der Buttons lassen sich wie auch schon der Titel in der Navigationsbar durch einen Doppelklick auf den Button editieren, vergebe dabei den Namen Abbrechen an den linken und Fertig an den rechten Button.
Abbildung 26:
Damit hast Du das Gerüst geschaffen, es fehlt der Code. Dazu erzeugst Du eine neue Klasse mit Rechtsklick auf den Ordner Fotoliste und wählst die Option new File, entscheidest Dich für ein iOS>Source>Cocoa Touch Class File und bestätigst den Dialog mit Next.
Du gibst der Klasse den Namen NotizEdit und deine neue Klasse ist eine Unterklasse von UIViewController ist. XCode ergänzt den Namen der Klasse automatisch um ein Controller, da dies den Gestalungskonventionen entspricht. Die Sprache der Klasse sollte auf Swift eingestellt sein.
Abbildung 27:
Du bestätigst den Speicherort innerhalb des Hauptverzeichnisses des Projekts. XCode öffnet anschließend die neue Klasse für Dich.
Den neuen Controller verbindest du mit dem auf dem Storyboard angelegten View. Nun erstellst Du für jedes UIElement ein Outlet. Das machst Du genau so wie bereits für den TableView.
@IBOutlet weak var bildAnzeige: UIImageView!
@IBOutlet weak var textFeld: UITextField!
@IBOutlet weak var fertigBtn: UIButton!
@IBOutlet weak var abbrechenBtn: UIButton!
Den Datenaustausch zwischen den beiden Controllern realisierst Du durch Felder. In diese kann ein Objekt gespeichert und infolgedessen innerhalb des zweiten Controllers editiert werden. Ein weiteres Feld liefert eine Referenz auf den TableView des Hauptcontrollers, da dieser aktualisiert werden soll sobald ein neuer Eintrag angelegt, bzw ein vorhandener editiert wird. Ergänze den NotizEditViewController um die Felder bildUndNotiz und liste.
var bildUndNotiz: BildUndNotiz?
var liste: UITableView?
Bei Start des NotizEditControllers möchtest Du den Text und das Bild, sofern vorhanden auch anzeigen. Du überschreibst dazu viewDidLoad und setzen darin die in bildUndNotiz enthaltenen Informationen in die jeweiligen UIElemente. Die Methode didReceiveMemoryWarning kannst du entfernen.
override func viewDidLoad() {
super.viewDidLoad()
textFeld.text = bildUndNotiz?.text
bildAnzeige.image = bildUndNotiz?.bild
}
An dieser Stelle fehlt noch ein Listener der reagiert wenn einer der beiden Knöpfe gedrückt wird. Du erstellst ein Outlet für den abbrechen Button, wählst dabei aber Action als Connection und verwendest knopfDruck als Namen. XCode erstellt Dir eine Methode knopfDruck mit passender Signatur.
Abbildung 28:
Diese Methode implementierst Du wie folgt.
@IBAction func knopfdruck(sender: AnyObject) {
var knopf = sender as UIButton
if fertigBtn == knopf {
if !textFeld.text.isEmpty {
if (bildUndNotiz != nil) {
bildUndNotiz!.text = textFeld.text
liste?.reloadData()
}
self.dismissViewControllerAnimated(true, completion: nil)
} else {
var alarm = UIAlertView()
alarm.title = "Fehler"
alarm.message = "Die Notiz darf nicht leer sein"
alarm.addButtonWithTitle("Ok")
alarm.show()
}
} else {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
Öffne das Storyboard erneut um eine Verbindung zwischen dem zweiten Button in der Methode knopfDruck herzustellen. Dazu öffnest du den Assistant Editor. Klicke den Fertig Button an und ziehe ein Outlet auf die neu erstellte Methode. XCode erkennt die passende Signatur der Methode und markiert diese mit einem blauen Kasten, lässt Du die Maustatse los, ist die Verbindung erstellt.
Verbinden der beiden Controller
Ein direkter Zugriff auf den Controller im Storyboard erhälst du durch eine eindeutige ID. Diese vergibst Du über den Unterpunkt StoryboardID im Identity Inspector. Als ID verwendest Du NotizEditViewControllerStory. Den Identity Inspector öffnest du mit selektiertem NotizEditViewController im MainStoryBoard durch einen Klick auf das blau markierte Symbol.
Abbildung 29:
Die Klasse ViewController ergänzt Du anschließend um die folgednde Methode:
func zeigeViewControllerFürBildUndNotiz (bildUndNotiz: BildUndNotiz) {
let notizEditController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("NotizEditViewControllerStory") as NotizEditViewController
notizEditController.bildUndNotiz = bildUndNotiz
notizEditController.liste = self.fotoListe
self.presentViewController(notizEditController, animated: true, completion: nil)
}
Die Idee ist es, den neuen Controller anzuzeigen, sobald der ImagePicker verschwunden ist. Dies realisierst Du, indem Du bei abgeschlossener schließ Animation des UIImagePickers den Befehl dazu gibst.
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
let chosenImage = info[UIImagePickerControllerEditedImage] as UIImage
let neuesBild = BildUndNotiz(bild: chosenImage, text: "")
bilderUndNotiz += [neuesBild]
self.fotoListe.reloadData()
picker.dismissViewControllerAnimated(true, completion:
{self.zeigeViewControllerFürBildUndNotiz(neuesBild)})
}
Während der Benutzung wird Dir auffallen, dass sich die Tastatur nicht schließt. Weder ein Drücken auf Return, noch ein Tippen auf den Bildschrim außerhalb des TextFields führt zum gewünschten Ergebnis. Das Problem gehst Du als nächstes an, solltest du das TextField bei geöffneter Tastatur bereits sehen können. Es kann je nach Layout vorkommen, dass die Tastatur die Eingabezeile verdeckt, das lässt sich beheben, indem du das TextField auf dem Storyboard weiter nach oben bewegst.
Automatisches Schließen der Tastatur
Für das Handling der Return Taste ist der UITextFieldDelegate zuständig, füge dieses Protokoll zur Klasse NotizEditViewController hinzu. Das Schließen der Tastatur erfolgt daher in der Methode des TextField. Implementiere diese wie folgt:
func textFieldShouldReturn(textField: UITextField) -> Bool {
textFeld.resignFirstResponder()
return true
}
Bleibt noch dem Textfeld mitzuteilen, dass der Controller als Delegate fungieren soll. Dies geschieht in viewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
textFeld.delegate = self
textFeld.text = bildUndNotiz?.text
bildAnzeige.image = bildUndNotiz?.bild
}
Zusätzlich ist es schön wenn sich die Tastatur ebenfalls schließt, sobald wir außerhalb des Textfeldes den Bildschirm berühren. Dazu verwenden wir einen UITapGestureRecognizer. Diesen erzeugst und verlinkst Du ebenfalls in viewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
textFeld.delegate = self
textFeld.text = bildUndNotiz?.text
bildAnzeige.image = bildUndNotiz?.bild
let berührungsdetektor = UITapGestureRecognizer(target: self, action: "schießeTastatur")
self.view.addGestureRecognizer(berührungsdetektor)
}
Fast fertig, es fehlt die Methode schließeTastatur, die ausgeführt wird, wenn der TapGestureRecognizer eine Berührung feststellt. Also füge diese hinzu.
func schließeTastatur(){
textFeld.resignFirstResponder()
}
Die Liste ist anklickbar, aber nichts passiert. Du würdest erwaten den Text des Bildes editieren zu können. Das funktioniert durch Implementierung einer Methode, welche sich um ClickEvents auf Liste kümmert, diese sieht wie folgt aus, und muss in der Klasse ViewController implementiert werden:
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
zeigeViewControllerFürBildUndNotiz(self.bilderUndNotiz[indexPath.row])
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
Damit bist Du fertig. Die App kann Bilder aus dem Speicher in einer Liste anzeigen, sich dazu Namen merken, und bietet die Möglichkeit diese zu editieren.
Fazit
XCode bietet wenig Überraschung, es ist einfach eine super Entwicklungsumgebung deren Autovervollständigung extrem schnell und sinnvoll ist. Mit Swift hat Apple eine Sprache gezaubert die mächtig ist und im Vergleich zu Objective-C eine angenehmere Syntax besitzt. Das gilt insbesondere für String-Operationen à la [myString stringByAppendingString:@"meine zweite Hälfte"]. Swift wirkt eleganter, leichter, ohne dabei Funktionen zu verlieren, es wird lediglich einfacher.
Hinzu kommt, dass eine Klasse in Swift in einer einzigen Datei beschrieben, und nicht aufgeteilt in .h- und .m-File. Dies macht ein Arbeiten mit Outlets angenehmer, denn ich muss nicht mehr zwischen m und h-File hin und her springen.
Für mich ein längst überfälliger Schritt, der das Programmieren für iOS noch einmal angenehmer macht, weshalb ich hoffe diese Sprache auch bald in den ersten Projekten nutzen zu dürfen.