NEW: Join my free 100 Days of SwiftUI challenge today! >>

Bilder laden mit UIImage

Nun haben wir einerseits den Table View Controller voller Bildernamen zur Auswahl, wie auch den Detail View Controller vorbereitet in unserem Storyboard. Im nächsten Schritt passen wir unseren Code so an, daß die Detailansicht auf den Fingertap reagiert und das ausgewählte Bild anzeigt.

Dafür überschreiben wir eine weiter Methode unseres Table View Controllers. Sie heißt tableView(_, didSelectRowAt:) und nimmt einen IndexPath entgegen, genauso wie vorher cellForRowAt. Das sagt uns, um welche Zeile es sich handelt. In dieser Methode müssen wir aber deutlich mehr als bisher machen:

  1. Wir brauchen eine Property in unserem DetailViewController in der der Dateiname des zu ladenden Bildes zur Verfügung gestellt wird.
  2. Wir implementieren in der Methode didSelectRowAt, daß unser DetailViewController aus dem Storyboard geladen wird.
  3. Abschliessend geben wir unserem DetailViewController eine viewDidLoad() in der das Bild laut dem übergebenen Dateinamen geladen und angezeigt wird.

Zuerst legen wir also die neue Property für den Bild-Dateinamen im DetailViewController an. Dateinamen legt man am besten als String ab, allerdings muß dieser hier optional sein, weil er nicht von Anfang an gesetzt werden kann. Zum Anlegen fügen wir folgendes in der Klasse DetailViewController unter die Zeile mit @IBOutlet an:

var selectedImage: String?

Nun weiter mit dem zweiten Schritt: wir implementieren didSelectRowAt so, daß ein DetailViewController aus dem Storyboard geladen und für die Anzeige vorbereitet wird. Als wir die Controller für die Detailansicht angelegt hatten, gaben wir ihm den ID “Detail”. Darüber können wir ihn mit der Storyboard-Methode instantiateViewController(withIdentifier:) laden und instanziieren. Jeder View Controller hat ein Property namens storyboard, in dem entweder nil oder das Storyboard abgelegt ist, aus dem heraus er erzeugt wurde. In unserem Fall wird es das Main.storyboard, also das selbe wie das unserer anderen View Controller.

Das läßt sich gut in drei Unterschritte aufteilen, wovon zwei neu für uns sind:

  1. Laden des Detail View Controller's aus unserem Storyboard.
  2. Füllen der Property selectedImage mit dem passenden Eintrag aus unserem pictures Array.
  3. Anzeige des neuen Views auf dem Bildschirm.

Für den ersten Schritt können wir instantiateViewController aufrufen, was uns aber zwei kleine Hürden entgegenstellt. Einmal rufen wir es über das storyboard property auf, das wir von Apple’s Klasse UIViewController geerbt haben. Aber dieses ist optional. Also müssen wir den ? Operator verwenden, wie vorher bei der Beschriftung (textLabel) unserer Tabellenzelle – “versuche das zu setzten, aber mache Nichts falls es ein Problem gibt.”

Die zweite Hürde stellt uns instantiateViewController(), weil es uns zwar den DetailViewController zurückgibt, wenn alles klappt, aber da die Methode geerbt wird, denkt Swift, das zurückgegebene wäre ein UIViewController. Es hat keine Kenntnis darüber, was alles im Storyboard ist.

Für einen Programmieranfänger ist das eher verwirrend. Ich versuche es anhand einer Analogie zu erklären: Stell Dir vor, wir verabreden uns für heute abend und Du bittest mich irgendwelche Tickets zu besorgen. Das mache ich auch und wenn wir uns treffen, gebe ich Dir Dein Ticket in einem Briefumschlag. Ich habe meinen Teil erfüllt – Du wolltest ein Ticket, ich habe eins besorgt. Aber was das für ein Ticket ist – ob für ein Fußballspiel, Kino, Theater oder sogar die Oper, bleibt vorerst unbekannt. Der einzige Weg für Dich das herauszufinden ist in den Umschlag zu schauen.

Swift hat hier das selbe Problem: Die Methode instantiateViewController() gibt laut Deklaration einen UIViewController (unsere Oberklasse) zurück. Aber wir wollen das in unsere Property vom Typ DetailViewController stecken. Die Lösung: Wir müssen Swift sagen, daß das was es hat etwas anderes ist als es denkt.

Der Fachbegriff dafür ist “typecasting”: wir bitten Swift einen Wert als einen anderen Typ zu verwenden, als ursprünglich vereinbart. Swift hat zwar viele Wege das zu machen, aber wir verwenden den Sichersten, was effektiv bedeutet: “bitte behandle das hier wie einen DetailViewController, aber wenn das schiefgeht, mache Nichts und mit der nächsten Code-Zeile weiter.”

Wenn wir endlich den Detail View Controller haben, können wir dessen selectedImage property auf pictures[indexPath.row], genauso wie damals mit dem Dateinamen in cellForRowAt – das war der einfache Teil.

Als dritten Unterschritt bringen wir die neue Detailansicht dazu sich anzuzeigen. Genauso wie jeder View Controller eine optionale Property storyboard hat, gibt es auch eine optionale für navigationController, den Navigation Controller, in den unser View eingebettet evtl. eingebettet ist.

Super für uns, denn solch ein Navigation Controller ist zuständig für das Anzeigen von Bildschirmseiten. Sie verwalten nämlich einen eventuell sehr grossen Stapel von Bildschirmseiten, durch den sich die Anwender vor und zurück bewegen/navigieren können. Am Anfang zeigt ein Navigation Controller den (ersten) View Controller, den wir ihm im Storyboard mit auf den Weg gegeben hatten. Aber sobald neue Seiten erzeugt werden, können wir sie auf seinen Stapel legen und sie werden weich hereinanimiert, ganz so wie Du es wohl von den iOS Einstellungen her kennst. Wenn ein Anwender dann zurück geht (durch Tap auf < oder durch eine Swipe-Bewegung von Links nach Rechts) entfernt der Navigation Controller diesen View vom Stapel und löscht ihn aus dem Speicher.

So – nach der Theorie nun der eigentliche Code. Bitte trage Folgendes in ViewController.swift ein (die Kommentare // … sollen die Unterschritte markieren):

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    // 1: versuchen den "Detail" View Controller zu laden und typecaste ihn zu einem DetailViewController
    if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController {
        // 2: Erfolg! Nun setze die selectedImage property
        vc.selectedImage = pictures[indexPath.row]

        // 3: übergib es an den Anzeigestapel des Navigation Controller
        navigationController?.pushViewController(vc, animated: true)
    }
}

Schauen wir uns die Zeile mit if let etwas genauer an. Dort gibt es drei Abschnitte, die misslingen könnten:

  • Die Property storyboard könnte nil sein (dann würde das ? den Rest der Zeile überspringen)
  • Der Aufruf instantiateViewController() könnte nichts gefunden haben (wenn wir z.B. nach “Fzzzzz” oder irgendwelchem anderen Unsinn gefragt hätten)
  • Und das Typecasten (das as?), weil vielleicht eine ganz andere Unterklasse zurückgegeben wurde.

Also gibt es drei Stellen wo potentiell etwas schief laufen kann (wenn Du alle meine Hinweise befolgt hast, wohl nicht). Deshalb ist das einleitende if let sehr hilfreich: Wenn in der Zeile etwas schief geht, wird der Code innerhalb der geschreiften Klammern nicht ausgeführt. Das bewahrt Dein Programm vor unsicheren Zuständen und Abstürzen.

Bevor wir uns das Ergebnis in der laufenden App ansehen können, müssen wir noch das Bild im DetailViewController anzeigen lassen.

Diese neue Code-Passage verwendet eine für uns neue Klasse namens UIImage. Sie hat kein “View” im Namen, also ist es wohl nichts was man direkt anzeigen lassen kann. Vielmehr ist UIImage die Klasse zum Laden von Bilddateien verschiedener Dateiformate wie PNG oder JPEG.

Beim Erzeugen eines UIImage übergeben wir einen Parameter namens named mit dem Dateinamen des zu ladenden Bildes. UIImage schaut dann nach dieser Datei im App-Bündel und lädt sie. Also übergeben wir hier das Property selectedImage das unser ViewController gesetzt hatte – das vom Anwender ausgewählte Bild. Aber wir können selectedImage nicht direkt verwenden, denn es wurde als Optional vereinbart, wie Du Dich vielleicht erinnerst:

var selectedImage: String?

Das ? bedeutet, es kann einen Wert beinhalten oder auch nicht. Und Swift lässt uns solche "Vielleicht"-Werte nicht ungeprüft verwenden. Nun können wir erneut if let verwenden, um die Verwendbarkeit von selectedImage zu prüfen. Falls verwendbar, setze damit die Konstante und führe den folgenden Block aus, sonst mache Nichts.

Füge folgenden Code in der Klasse DetailViewController in der Methode viewDidLoad() an, und zwar hinter dem Aufruf von super.viewDidLoad():

if let imageToLoad = selectedImage {
    imageView.image  = UIImage(named: imageToLoad)
}

Die erste Zeile prüft erst den optionalen Wert in selectedImage und setzt ggf. die Konstante auf ihren Wert. Falls aus irgendeinem Grund selectedImage doch nil enthält, würde auch die nachfolgende Zeile (der Block) nicht ausgeführt werden.

Wenn ein Wert vorhanden ist, wird imageToLoad auf ihn gesetzt und zum Erzeugen/Laden des Bildes in UIImage(named:) verwendet.

So, das war’s – drücke nun Cmd+R und probiere die App aus! Du solltest einens der Bilder auswählen können, das dann vollflächig seitlich hereingeschoben wird.

Beachte, daß uns der Navigation Controller auch einen Zurück-Knopf < bereitstellt, der uns zurück bringt zum ViewController. Das selbe sollte auch über die Swipe-Geste machbar sein. Wenn Du mit der Maus ganz Links klickst und nach Rechts draggst, wie mit Deinem Daumen auf einem iPhone.

LEARN SWIFTUI FOR FREE I have a massive, free SwiftUI video collection on YouTube teaching you how to build complete apps with SwiftUI – check it out!

MASTER SWIFT NOW
Buy Testing Swift Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!