UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Auflisten von Bildern mit dem Dateimanager (FileManager)

Die Bilder, die ich Dir bereitgestellt habe, stammen von der Staatlichen Verwaltung für Meere und Atmosphären (NOAA), einer US Behörde - deren öffentliche Inhalte für jedermand frei nutzbar sind (Public domain). Sobald Xcode sie in Dein Projekt kopiert hat, kann Deine App auf sie zugreifen.

Hinter der Bühne ist eine iOS Application nur ein spezielles Verzeichnis mit vielen Dateien: Zu aller erst die kompilierte Version Deines Programm-Codes, das sog. Binary (die ausführbare Binärdatei). Dann alle Medien, die verwendet werden, alle Dateien für das sichtbare Layout, sowie eine Reihe weiterer Dateien wie Meta-Daten und Sicherheits-Einstellungen.

Solche Applikations-Verzeichnisse nennt man Bundles (Bündel) und sie tragen ".app" als Dateinamen-Erweiterung. Weil unsere Medien direkt in diesem Verzeichnis liegen, können wir das Betriebsystem fragen, was alles vorhanden ist und auch den Inhalt anfordern. Vielleicht ist Dir aufgefallen, daß alle Bild-Dateien mit dem Namensvorsatz "nssl" beginnen (das ist die Abkürzung von "National Severe Storms Labaratory"). Das macht unsere nun zu lösende Aufgabe einfacher: Liste alle Dateien im App-Bundle und stelle alle bereit, deren Name mit "nssl" beginnt.

Vorerst werden wir die Namensliste nur in Xcode's eingebaute Logging(Log)-Anzeige ausgeben lassen, aber später lassen wir sie auch in der App erscheinen.

Schritt 1: Öffne "ViewController.swift". Einen View-Controller stellt man sich am besten vor als einen Bildschirminhalt mit Informationen, und das ist für uns derzeit ein leerer Bildschirm. Der Code in "ViewController.swift" ist dafür zuständig, diesen leeren Bildschirm darzustellen und sollte derzeit recht übersichtlich sein. Etwa so:

import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Weitere Vorbereitungen nach dem Laden des View, meist aus einer nib-Datei
    }
}

Darin stecken gleich fünf interessante Dinge, die ich erklären möchte, bevor wir weitermachen.

  1. Der Code beginnt mit import UIKit, was meint: "Diese Datei verwendet die Zusatz-Bibliothek iOS user interface toolkit (die iOS Benutzer-Schnittstelle)"
  2. Die Zeile class ViewController: UIViewController bedeutet: "Ich schaffe eine Klasse namens ViewController, die von der Klasse UIViewController erbt." Sobald etwas mit UI anfängt, stammt es höchstwahrscheinlich aus der Sammlung UIKit. UIViewController ist Apple’s Standard-Anzeige-Kontroller, der solange leer und weiß hinterlegt bleibt, bis wir das in einer Unterklasse ändern.
  3. Die Zeile override func viewDidLoad() leitet eine Methode (einen benannten Code-Block) für unsere neue ViewController-Anzeige ein. Das Schlüsselwort override wird gebraucht, weil wir damit sagen: "wir wollen Apple’s Standard-Verhalten aus UIViewController überschreiben." Die Methode viewDidLoad() wird aufgerufen, sobald die Bildschirmanzeige bereit für unsere angepaßten Ausgaben ist.
  4. Da sind dann noch viele Zeichen { und }. Diese geschweiften Klammern (braces / curly brackets) begrenzen und gruppieren Teile des Codes und es hat sich bewährt die Zeilen dazwischen deutlich sichtbar nach rechts einzurücken. Die äussersten Klammern umfassen den gesamten Daten-Typ ViewController und die inneren markieren Beginn und Ende der Methode viewDidLoad().
  5. Die Methode enthält bislang nur eine aktive Befehlszeile mit super.viewDidLoad() und einen einzeiligen Kommentar, der mit // eingeleitet wird. Dieser Aufruf von super bedeutet: "rufe zuerst die Methode von Apple’s Klasse UIViewController auf, bevor ich etwas mache". Uns wird dieses Vorgehen noch sehr oft begegnen.

Dieser Programmteil wird uns auch in weiteren Projekten wiederbegegnen. Lass' Dich nicht einschüchtern, falls Du noch nicht Alles verstehst.

Du siehst keine Zeilennummern? Zum Lesen von Code ist es oft hilfreich, wenn die Zeilennummern mit angezeigt werden. Dann können wir uns auf Zeilen eindeutiger beziehen. Falls Dir Xcode nicht bereits Zeilennummern anzeigt, schlage ich vor wir aktivieren das jetzt: Öffne das Menü [Xcode] und dort [Preferences] (Einstellungen). Aktiviere unter dem Reiter [Text Editing] den ersten Punkt: Show "Line numbers".

Wie bereits erwähnt, wird die Methode viewDidLoad() aufgerufen, sobald die App die Anzeige vorbereitet hat. Alles zwischen func viewDidLoad() { und der der nächsten schliessenen geschweiften Klammer } ein paar Zeilen weiter wird dann ausgeführt werden.

Wir werden nun die Befehle zum laden der NSSL-Bilder einbauen: Fügen die folgenden Zeilen ein unter der Zeile super.viewDidLoad():

let fm = FileManager.default
let path = Bundle.main.resourcePath!
let items = try! fm.contentsOfDirectory(atPath: path)

for item in items {
    if item.hasPrefix("nssl") {
        // dieses Bild wollen wir laden!
    }
}

Hinweis: Wahrscheinlich wollen mir erfahrene Swift-Entwickler wegen dem try! gleich eine erzürnte eMail schreiben. Bitte lest erst einmal weiter.

Das war ein etwas größeres Code-Segment mit viel Neuem. Lasst uns das Zeile für Zeile gemeinsam durchgehen:

  • In der Zeile let fm = FileManager.default deklarieren wir eine Konstante mit dem Namen fm und weisen ihr das Ergebnis aus dem Aufruf von FileManager.default zu. Dieser Daten-Typ erlaubt uns das Arbeiten mit dem Dateisystem - in unserem Fall verwenden wir es für Dateien.
  • Die Zeile let path = Bundle.main.resourcePath! deklariert eine weitere Konstante namens path die auf den Resourcen-Pfad unseres App-Bündels zeigt. Zur Erinnerung: Alle Dateien unserer fertigen App liegen in dem .bundle und dessen Resource-Pfad zeigt auf die von uns hinzugefügten Bilder.
  • Die Zeile let items = try! fm.contentsOfDirectory(atPath: path) vereinbart eine dritte Konstante namens items, die mit den Einträgen dieses Verzeichnisse gefüllt wird. Welches Verzeichnis? Nun, das was wir eine Zeile davor angefragt hatten. Wie Du vielleicht erkennst, erklären Apple's sehr lange Methoden-Namen selbst recht gut was hier passiert! Die Konstante items ist ein Array - eine Sammlung - der Namen aller Dateien die im "resource"-Verzeichnis unserer App gefunden wurden.
  • In der Zeile mit for item in items { starten wir eine Schleife. Eine Schleife ist ein Code-Block der mehrfach ausgeführt wird. In diesem Fall wird der Code für jeden Eintrag (item) von items ausgeführt. Beachte, daß am Ende der Zeile eine öffnende geschweifte Klammer steht. Die beginnt den neuen Code-Block, der mit der passenden, schliessenden Klammer } beendet wird. Alles dazwischen wird bei jeweils einem Schleifendurchlauf ausgeführt. Die Zeile mit for kann also wie folgt gelesen werden: "Behandle items als eine Reihe von Zeichenketten, stecke nacheinander jede Zeichenkette (String) in die Variable item und arbeite den nun folgenden Code-Block damit ab ..."
  • Die Zeile if item.hasPrefix("nssl") { ist die erste Zeile in diesem Schleifen-Block. Zu diesem Zeitpunkt steht der Name der ersten Datei in der Variable item. Um entscheiden zu können, ob uns diese Datei hier interessiert, verwenden wir die Methode hasPrefix(): Sie verwendet den Aufrufparameter (die Buchstabenfolge mit der unsere gewünschten Bildernamen beginnen) und gibt entweder true oder false (Wahr/Falsch) zurück. Das if am Anfang leitet eine sog. Bedingte Anweisung ein. Falls die Bedingung nach dem if erfüllt ist, wird - du ahnst es - ein neuer Code-Block ausgeführt (umfasst von { und }). Aber nur dann. Also wird die nächste Zeile nur ausgeführt, wenn der Dateiname mit "nssl" beginnt.
  • Aber diese Zeile ist z.Zt. noch ein Kommentar. An dieser Stelle sollten wird uns den Namen in item irgendwo anders merken oder verarbeiten.

In diesen paar Zeilen war einiges Neues. Bevor wir weitermachen, fasse ich noch einmal zusammen:

  • Wir verwenden das Schlüsselwort let um Konstanten zu deklarieren. Konstanten halten Daten die wir zwar verwenden wollen, die sich aber später nicht mehr ändern sollen. Dein Geburtsdatum wäre ein Beispiel für solch eine Konstante. Das ändert sich nicht. Wohl aber Dein Alter als Zeitangabe. Das wäre eine Variable, weil es sich genaugenommen über die Zeit des Programmablaufens verändert.
  • Swift Entwickler verwenden gerne solche Konstanten, wenn nur ein Startwert verwendet wird. Sie helfen dem Compiler den Code zu optimieren und zu verschnellern und er warnt uns mit einem Fehler, wenn unser Code später doch versucht etwas hineinzuschreiben.
  • In Swift verwenden wir den Datentyp String für alles was mit Text zu tun hat. Solche Zeichenketten werden hochoptimiert verarbeitet und wir können sie mit allen denkbaren Sprachen füllen - Englisch, Deutsch mit allen Umlauten, Chinesisch, Klingonisch, Emojis und mehr...
  • Ansammlungen von Werten des selben Typs nennen wir Array. Ein Array von strings (Zeichenketten) deklarieren wir mit eckigen Klammern als [String]. Falls Du versuchts eine Ganzzahl statt einer Zeichenkette in dieses Array zu stecken, wird Xcode sich weigern.
  • Das Schlüsselwort try! besagt "der folgende Code könnte eventuell einen Fehler werfen, aber ich bin mir absolut sicher, daß er das nicht macht. Falls er das dennoch machen sollte - in unserem Fall, falls das Verzeichnis, das wir angefragt haben, nicht vorgefunden wird - wird unsere App abstürzen.
  • In diesem Fall ist es völlig in Ordnung try!zu verwenden, denn wenn die App ihre eigenen Daten nicht lesen kann, muss schon etwas extrem schief gelaufen sein! Manche Swift Programmierer versuchen Code zu schreiben der solche Fälle behandelt, aber damit verstecken sie oft ein noch viel schwerer wiegendes Problem.
  • Wir können for someItem in someArray verwenden, um alle Elemente eines Arrays abzuarbeiten. Dabei wird jedes Element nacheinander in die Hilfsvariable gesteckt und damit der Schleifen-Code durchlaufen.

Falls Du besonders aufmerksam warst, fiel Dir auch ein ganz kleines, weiteres Detail auf, das aber auch eines der kompliziertesten Themen in Swift betrifft: Das Ausrufezeichen am Ende von Bundle.main.resourcePath!.

Nein, das war kein Tippfehler meinerseits. Wenn Du das ! wegnimmst, compiliert Xcode den Code nicht mehr. Es ist also sehr wichtig. Nun - Swift hat drei Arten mit Daten umzugehen:

  1. Eine Variable oder Konstante die die Daten speichert. Zum Beispiel, name: String ist eine Zeichenkette namens name.
  2. Eine Variable oder Konstante die die Daten speichern könnte, aber nicht immer sind diese verfügbar. So etwas nennen wir einen optionalen Typ und deklarieren es als name: String?. Solche Daten können wir nicht direkt verarbeiten, sondern müssen Swift erst prüfen lassen, ob wirklich ein Wert vorhanden ist.
  3. Eine Variable oder Konstante die einen optionalen Wert halten sollen, aber wir sind 100% sicher, daß dieser Wert gesetzt ist - jedenfalls wenn wir es selbst anfangs gemacht haben. Das nennen wir ein implizit entpacktes Optional und wir schreiben das als name: String!. Auf soetwas können wir direkt zugreifen.

Wenn ich das Jemandem zum ersten Mal erkläre, verwirrt das die Meisten. Wir werden solche Optionals noch sehr oft verwenden und mit der Zeit wird Dir das Konzept sicher klarer werden. Wir gehen später noch genauer darauf ein.

Für's Erste betrachten wir: Der Ausdruck Bundle.main.resourcePath kann etwas zurückgeben oder auch nicht, also eine optionale Zeichenkette vom Typ String?. Indem wir den Ausdruck mit einem ! abschliessen, erzwingen wir das Entpacken der optionalen Inhaltes. Das heißt soviel wie: "Ich bin mir sicher, ich bekommen eine Zeichenkette und es wird nie nil sein, also bitte gib es mir als eine normale Zeichenkette."

Wichtige Warnung: Wenn Du eine Konstante oder Variable liest, die dennoch nil enthält, wird Deine App abstürzen. Deshalb nennen Manche ! den "Absturz-Operator" weil man es leicht falsch machen kann. Das Gleiche gilt für das try! von vorhin. Aber keine Sorge - mit der Zeit wird Du sicherer damit umgehen und es verstehen.

Unser Code lädt also die Liste der Dateien im resource-Pfad der App, schleift über alle Namen, findet die, die mit "nssl" beginnen, macht dann aber nichts Sinnvolles damit. Deshalb sollte der nächste Schritt sein, ein neues Array mit den "nssl"-Bildernamen anzulegen, damit wir diesen Vorgang der Filterung später nicht immer wiederholen müssen.

Die drei Konstanten, die wir bereits angelegt hatten, fm, path, und items leben innerhalb der Methode viewDidLoad() und werden zerstört, sobald die Methode beendet wird. Wir brauchen also einen Weg, wie wir Daten dem übergeordneten Typ ViewController (der Instanz einer Klasse) zuordnen, damit die Daten solange leben wie der. In Swift verwenden wir dazu ein sog. "property". Wir können dem ViewController beliebig viele solcher Properties geben und diese dann sooft schreiben und lesen, solange der existiert.

Eine property deklarieren wir ausserhalb von Methoden. Bislang waren unsere Datenstrukturen alle Konstanten. Dieses neue Array wollen wir innerhalb der Schleife schreibend befüllen, also verwenden wir nun das Schlüsselwort var zum Erzeugen einer Variable. Wir müssen ausserdem Swift genau beschreiben, welchen Datentyp wir im Array verwalten wollen. In diesem Fall ist es ein Array von Zeichenketten mit allen Namen, die mit "nssl" beginnen.

Füge diese Zeile vor der Zeile mit func viewDidLoad() { ein:

var pictures = [String]()

Danach sollte der Code für ViewController so anfangen:

class ViewController: UIViewController {
    var pictures = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()

        let fm = FileManager.default

Das Schlüsselwort var wird zum Anlegen von Variablen verwendet, vergleichbar zu let für Konstanten. Aber der letzte Teil der Zeile sieht ungewöhnlich aus: [String]().

Das sind eigentlich zwei Dinge auf einmal: [String] bedeutet "eine Array von Zeichenketten" und das () dahinter bedeutet "erzeuge eines". Die Klammern stehen genau wie bei viewDidLoad() dafür, daß etwas unter einem Namen aufgerufen werden soll. In unserem Falle - das Erzeugen eines String-Array’s.

Dieses Array namens pictures wird zukünftig zusammen mit dem ViewController erzeugt werden und so lange wie der Controller verfügbar sein. Es ist leer, aber wir können es in unserem Code befüllen.

Dazu ersetzen wir // dieses Bild wollen wir laden! durch den Befehl, den Namen an das Array anzuhängen (engl.: to append). Sinnvollerweise gibt es bei Arrays bereits die Methode append die wir aufrufen können. Wir ersetzen also den Kommentar durch :

pictures.append(item)

Das war’s auch schon! Wenn wir die App nun kompilieren sieht es blöderweise so aus, daß all’ unsere Arbeit umsonst gewesen sein könnte - denn der Simulator zeigt weiterhin nur einen leeren Bildschirm.

Um zu prüfen was passiert, fügen wir vor das Ende unserer Methode noch ein:

print(pictures)

Das veranlaßt Swift den Inhalt von pictures in der Debug-Console von Xcode auszugeben. Wenn wir nun die App starten sollte Folgendes dort erscheinen:

["nssl0033.jpg", "nssl0034.jpg", "nssl0041.jpg", "nssl0042.jpg", "nssl0043.jpg", "nssl0045.jpg", "nssl0046.jpg", "nssl0049.jpg", "nssl0051.jpg", "nssl0091.jpg”]

Hinweis: iOS gibt gerne viele und für uns vorerst uninteressante Dinge in der Debug-Console aus. Kümmer Dich nicht darum und scrolle soweit runter, bis unsere Namensliste sichtbar wird.

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS 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!

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.