Holger Hinzberg
Kapitel 15
Lotto – 6 aus 49
Kapitel 16
Ein Blick hinter die Kulissen
Kapitel 17
Eingaben mit virtuellen Tastaturen
Kapitel 18
Schieberegler, Textformatierungen und unendliche Werte
Kapitel 19
Storyboards – Mit dem Drehbuch durch die App
Kapitel 20
Storyboards mit Protokollen
Kapitel 21
Navigation mit einem Tableisten-Controller
Kapitel 22
Grafische Oberflächen ohne Interface Builder
Kapitel 23
Serialisierung – Aus dem Speicher in eine Datei
Kapitel 24
Der Picker – ein ganz besonderes Steuerelement
Kapitel 25
Tabellen
Kapitel 26
Eye Candy – Unsere App soll schöner werden
Kapitel 27
Der Collection View
Kapitel 28
Zeichnen mit Core Graphics
Kapitel 29
Multi-Touch mit Gestenerkennung
Kapitel 30
Digitale und analoge Uhren mit Timern
Kapitel 31
Lokale Benachrichtigungen
Kapitel 32
Karten und Koordinaten
Kapitel 33
Daten suchen und finden
Kapitel 1
Datentypen und Optionals
Kapitel 2
Zeichenketten
Kapitel 3
Arrays und Dictionaries
Kapitel 4
Fallunterscheidungen und Schleifen
Kapitel 5
Funktionen
Kapitel 6
Closures
Kapitel 7
Klassen und Objekte
Kapitel 8
Methoden
Kapitel 9
Vererbung und Assoziationen
Kapitel 10
Protokolle und Extensions
Kapitel 11
Strukturen und Enumerationen
Kapitel 12
Sicherer Programmcode
Kapitel 13
Speicherverwaltung mit Referenzzähler
Kapitel 14
Robuste Anwendungen und automatisierte Tests
// Variablen und Konstanten var variableValue = 3.1415 variableValue = 3.0 let constValue = 42 // Neue Zuweisung nicht möglich // constValue = 50
var salary = 10000 if salary > 15000 { print("Zu viel") } else if salary < 10000 { print("Zu wenig") } else { print("Ok") } var index = 4 switch index { case 1: print("Index ist 1") case 2: print("Index ist 2") case 3...5: print("Index liegt zwischen 3 und 5") case 6: print("Index ist 6") fallthrough default: print("Index liegt nicht zwischen 1 bis 5") }
for var index = 0; index <= 5; index++ { print(index) // Ausgabe von 0 bis 5 } for index in 0...5 // Closed Range { print(index) // Ausgabe von 0 bis 5 } for index in 0..<5 // Half-Open Range { print(index) // Ausgabe von 0 bis 4 } for index in (5...10).reverse() { print(index) // Ausgabe von 10 bis 5 } var access = 1 repeat { print(access) // Ausgabe von 1 bis 9 access++ } while access < 10
var cities:[String] = ["Rom","Paris","Berlin"] cities.append("London") for city in cities { print(city) // Ausgabe aller Städtenamen } var count = cities.count // Anzahl der Elemente
// Ohne Parameter und ohne Rückgabewert func doSomething() { print("Hello World!") } doSomething() // Mit einem Double als Parameter und einem Double // als Rückgabewert func doSomething(value:Double) -> Double { return value * 2.0 } var twice = doSomething(4.5) // Mit zwei benannten Parametern // und einem Double als Rückgabewert func doSomething( firstValue first:Double, secondValue second:Double) -> Double { return first + second } var sum = doSomething(firstValue: 3.2, secondValue: 4.7)
var animals:[Int: String] = [11:"Hund", 22:"Katze", 33:"Maus"] animals[22] = "Elefant" // Element ersetzen animals[99] = "Huhn" // Element anfügen print(animals[22]) // Ausgabe "Optional (Elefant)" print(animals[99]) // Ausgabe "Optional (Huhn)" animals[11] = nil // Element entfernen
var firstName = "Mike" var lastName = "Müller" // String-Interpolation print("Mein Name ist \(firstName) \(lastName).") // Zahlenwerte formatieren var numeric = 3.34983632 var num = String(format:"%.2f", numeric) // Ausgabe 3.35
class Person { // Eigenschaften var firstName:String var lastName:String init() { // Eigenschaften initialisieren firstName = "" lastName = "" } } // Klasseninstanz erzeugen und // Eigenschaften zuweisen var pers = Person() pers.firstName = "Mike" pers.lastName = "Müller"
enum Color { case Red case Green case Blue } var exterior:Color = Color.Green
// Eigene Fehlertypen definieren enum CalculationError : ErrorType { case DivideByZero case OtherError } // Funktion kann einen Fehler zurückgeben func divide(dividend:Int, divisor:Int) throws -> Int { guard divisor > 0 else { throw CalculationError.DivideByZero } return dividend / divisor } // Funktionsaufruf und Fehlerbehandlung do { var result = try divide(100, divisor: 0) } catch(CalculationError.DivideByZero) { print("Es wurde versucht, durch null zu teilen") } catch { print("Ein anderer Fehler ist aufgetreten") }
Bibliografische Information der Deutschen Nationalbibliothek
Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über <http://dnb.d-nb.de> abrufbar.
ISBN 978-3-95845-223-7
1. Auflage 2016
www.mitp.de
E-Mail: mitp-verlag@sigloch.de
Telefon: +49 7953 / 7189 - 079
Telefax: +49 7953 / 7189 - 082
© 2016 mitp Verlags GmbH & Co. KG
Dieses Werk, einschließlich aller seiner Teile, ist urheberrechtlich geschützt. Jede Verwertung außerhalb der engen Grenzen des Urheberrechtsgesetzes ist ohne Zustimmung des Verlages unzulässig und strafbar. Dies gilt insbesondere für Vervielfältigungen, Übersetzungen, Mikroverfilmungen und die Einspeicherung und Verarbeitung in elektronischen Systemen.
Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Werk berechtigt auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften.
Lektorat: Sabine Schulz
Sprachkorrektorat: Petra Heubach-Erdmann
Coverbild: © Max Krasnov @ fotolia.de
electronic publication: III-satz, Husby, www.drei-satz.de
Dieses Ebook verwendet das ePub-Format und ist optimiert für die Nutzung mit dem iBooks-reader auf dem iPad von Apple. Bei der Verwendung anderer Reader kann es zu Darstellungsproblemen kommen.
Der Verlag räumt Ihnen mit dem Kauf des ebooks das Recht ein, die Inhalte im Rahmen des geltenden Urheberrechts zu nutzen. Dieses Werk, einschließlich aller seiner Teile, ist urheberrechtlich geschützt. Jede Verwertung außerhalb der engen Grenzen des Urheherrechtsgesetzes ist ohne Zustimmung des Verlages unzulässig und strafbar. Dies gilt insbesondere für Vervielfältigungen, Übersetzungen, Mikroverfilmungen und Einspeicherung und Verarbeitung in elektronischen Systemen.
Der Verlag schützt seine ebooks vor Missbrauch des Urheberrechts durch ein digitales Rechtemanagement. Bei Kauf im Webshop des Verlages werden die ebooks mit einem nicht sichtbaren digitalen Wasserzeichen individuell pro Nutzer signiert.
Bei Kauf in anderen ebook-Webshops erfolgt die Signatur durch die Shopbetreiber. Angaben zu diesem DRM finden Sie auf den Seiten der jeweiligen Anbieter.
In der Familie der Programmiersprachen, die von der Sprache C abstammen, hat Objective-C eine Außenseiterposition, denn die Sprache unterscheidet sich teilweise sehr von ihren Geschwistern. Swift hat einige der Eigenschaften übernommen und verfügt ebenfalls über eine spezielle Art von Datenzugriff, die in anderen Programmiersprachen nicht oder komplett anders umgesetzt werden. Falls Sie bisher ausschließlich mit anderen Sprachen gearbeitet haben, erwarten Sie in diesem Kapitel einige Überraschungen.
Auf den folgenden Seiten werden wir zunächst keine App entwickeln, trotzdem sollten Sie alle vorgestellten Beispiele programmieren und dabei mit den Anweisungen spielen und andere Parameter oder andere Werte ausprobieren. Zum Experimentieren gut geeignet ist die main.swift
-Datei einer OS-X-Konsolenanwendung (Command Line Tool). Der Code dort wird beim Start der Anwendung automatisch ausgeführt. Zusätzliche Interaktionen, wie das Auswählen einer Schaltfläche, sind dann nicht notwendig.
Sollen die Instanzvariablen eines Objekts manipuliert werden, geschieht das in der Regel über einen Zugriff auf die öffentlichen Eigenschaften der Klasse oder über Methoden. Beide Möglichkeiten unterstützen das Konzept der Kapselung und erlauben Veränderungen ausschließlich über definierte »Zugänge«. Soll eine Instanzvariable von außerhalb der Klasse aus nur gelesen werden können, lässt sich das ebenfalls mit geringem Aufwand konfigurieren. Swift unterscheidet sich dort nur wenig von anderen Programmiersprachen.
Eine Besonderheit ist das Key Value Coding-(KVC-)Verfahren, bei dem der Zugriff auf die Eigenschaften über deren Namen als Zeichenkette geschieht. Das mag im ersten Moment seltsam erscheinen und unverständlich klingen – KVC schafft in der Entwicklung aber zusätzliche Möglichkeiten, die es erlauben, mit geschickter Programmierung viele Codezeilen einzusparen. Erinnern wir uns zunächst, wie wir bisher gearbeitet haben.
let truck = Truck() // Zugriff mit Punktnotation truck.make = "Megatrucker" truck.color = "Schwarz" truck.wheels = 4 print("Marke: \(truck.make)") print("Farbe: \(truck.color)") print("Anzahl der Räder: \(truck.make)")
Im Listing geschieht der lesende und schreibende Zugriff auf die Eigenschaften der Klasse Truck
mit der Punktnotations-Syntax. Für moderne Projekte ist das die leichteste Art der Programmierung. Die Entwicklungsumgebung unterstützt die Schreibweise und unterbreitet während der Eingabe Vorschläge.
Eine alternative Möglichkeit, der Zugriff über Key Value Coding, verlangt eine andere Syntax. Dort kommen für einen Zugriff die Methoden setValue(_: forKey:)
und valueForKey
zum Einsatz. Der Name einer Eigenschaft wird über den Parameter Key
und als Typ String
übergeben.
let truck = Truck() // Zugriff mit KVC truck.setValue("Megatrucker", forKey: "make") truck.setValue("Schwarz", forKey: "color") truck.setValue(4, forKey: "wheels") print("Marke: \(truck.valueForKey("make"))") print("Farbe: \(truck.valueForKey("color"))") print("Anzahl der Räder: \(truck.valueForKey("wheels"))")
Im Gegensatz zur Punktnotation funktioniert Key Value Coding in der aktuellen Swift-Version ausschließlich mit Klassen, die in ihrem Stammbaum von NSObject
abgeleitet sind. Außerdem kann KVC mit Zahlendatentypen nur arbeiten, wenn sie keine Optionals sind. Der Rückgabewert von valueForKey
ist allerdings immer ein Optional vom Typ AnyObject
. Die verwendete Klasse Truck
sieht so aus:
import Foundation class Truck: NSObject { var make:String? var color:String? var wheels:Int = 0 }
Die Frage, warum man auf diese Art programmieren sollte, lässt sich einfach beantworten: Tun Sie es nicht! KVC mit unveränderlichen Zeichenketten bringt keine Vorteile und trägt auch nicht zur besseren Lesbarkeit des Programmcodes bei. Interessant wird das Verfahren, sobald die Namen der Eigenschaften in Variablen oder Konstanten abgelegt werden.
let truck = Truck() // Zugriff mit Punktnotation let makeKey = "make" let colorKey = "color" let wheelsKey = "wheels" // Zugriff mit KVC truck.setValue("Megatrucker", forKey: makeKey) truck.setValue("Schwarz", forKey: colorKey) truck.setValue(4, forKey: wheelsKey) print("Marke: \(truck.valueForKey(makeKey))") print("Farbe: \(truck.valueForKey(colorKey))") print("Anzahl der Räder: \(truck.valueForKey(wheelsKey))")
Mit Variablen, die unveränderlich im Code definiert sind, erreicht man ebenfalls keine Flexibilität im Programm, aber vielleicht erkennen Sie jetzt die Möglichkeiten. Der Variablenwert kann die Eingabe eines Anwenders oder der Parameter einer Methode sein, woraufhin dann eine andere Eigenschaft ausgegeben wird. Andere Einsatzmöglichkeiten ergeben sich, wenn mehrere Schlüssel in einem Array zusammengefasst werden. Mit einer Schleife ist es dann leicht, sämtliche Eigenschaften eines Objekts auszugeben. Das folgende Listing zeigt, wie das in einfacher Form funktioniert.
let keys = ["make", "color", "wheels"] for key in keys { print("Wert: \(key) für Key: \(truck.valueForKey(key)!)") }
Leider kommt Key Value Coding nicht ohne Gefahren. Weil der Name der Eigenschaften über eine Zeichenkette definiert wird, ist eine Überprüfung durch den Compiler nicht mehr möglich. Erneut treffen wir auf String Dependencies, die Abhängigkeit von Zeichenketten. Verwendet man Bezeichner, die keinem Eigenschaftennamen entsprechen, führt das unweigerlich zu einem Programmfehler. Probieren Sie die folgende Anweisung aus:
truck.setValue("Mike", forKey: "driver")
Die Klasse Truck
verfügt über keine Eigenschaft mit dem Namen driver
, was vom Compiler bei der Übersetzung des Programmcodes nicht bemerkt wird. Er kann die KVC-Schreibweise nicht analysieren. Erst zur Laufzeit macht sich das Problem bemerkbar und Xcode schreibt eine umfangreiche Fehlermeldung auf die Konsole. Für die Klasse Truck
ist es nicht möglich, mit Key Value Coding auf eine Eigenschaft mit dem Namen driver
zuzugreifen:
setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key driver.
Viele Klassen verwenden für ihre Eigenschaften wiederum Klassen, und möchte man dann auf Eigenschaften zugreifen, bilden sich Schlüsselpfade (englisch: Key Path), bei denen die Namen der einzelnen Eigenschaften zu einer langen Kette verbunden werden. Das können wir leicht ausprobieren. Erweitern Sie Ihr Projekt um die nachfolgende Klasse Engine
und die Klasse Truck
dann um eine Eigenschaft dieser Klasse.
import Foundation class Engine: NSObject { var fuel:String? var power:Int = 0 } Die erweiterte KlasseTruck
: import Foundation class Truck: NSObject { var make:String? var color:String? var wheels:Int = 0var engine = Engine()
}
Sowohl beim schreibenden als auch beim lesenden Zugriff werden die verschiedenen Eigenschaften mit der Punktnotation aneinandergereiht.
let truck = Truck() truck.engine = Engine() truck.engine.power = 125 truck.engine.fuel = "Diesel" print("Leistung: \(truck.engine.power)") print("Kraftstoff: \(truck.engine.fuel)")
Der Schlüsselpfad, also der Weg zur gewünschten Eigenschaft, ist für die Kraftstoffart truck.engine.fuel
und für die Leistung truck.engine.power
. Für vergleichbare Zugriffe mit KVC bietet das Framework die Methode valueForKeyPath
an. Der Rückgabetyp ist auch dort wieder ein Optional AnyObject
.
print("Leistung: \(truck.valueForKeyPath("engine.power"))") print("Kraftstoff: \(truck.valueForKeyPath("engine.fuel"))")
Ist eine Eigenschaft eine Auflistung, zum Beispiel ein Array, schaffen die Schlüsselpfade zusätzliche Wege, Informationen von einem Objekt zu erhalten. Leicht ausprobieren können wir das, indem wir das Projekt um eine zusätzliche Swift-Klasse mit dem Namen Destination
erweitern, in der die verschiedenen Ziele für unseren LKW abgelegt werden. Um das Beispiel einfach zu halten, verfügt die Klasse nur über die Eigenschaften distance
für die Entfernung und destinationDescription
für eine Bezeichnung. Den Namen description
können wir für eine Eigenschaft nicht verwenden. Sie wurde bereits in der Elternklasse deklariert und hat andere Aufgaben.
import Foundation class Destination: NSObject { var distance:Int = 0 var destinationDescription:String? }
Weil ein LKW nicht nur ein einziges Ziel ansteuern kann, ist die neue Eigenschaft destinations
in der Datei Truck.swift
nicht vom Typ Destination
, sondern ein Array
, das mehrere Destination
-Objekte aufnehmen kann.
import Foundation
class Truck: NSObject
{
var make:String?
var color:String?
var wheels:Int = 0
var engine = Engine()
var route = [Destination]()
}
Jetzt ist es kein Problem mehr, einem Truck
-Objekt ein oder mehrere Ziele zuzuweisen.
let truck = Truck() var des = Destination() des.destinationDescription = "Bahnhof" des.distance = 100 truck.destinations.append(des) des = Destination() des.destinationDescription = "Hafen" des.distance = 75 truck.destinations.append(des) des = Destination() des.destinationDescription = "Flugplatz" des.distance = 245 truck.destinations.append(des) des = Destination() des.destinationDescription = "Hafen" des.distance = 130 truck.destinations.append(des)
Sollen anschließend sämtliche Ziele ausgegeben werden, so ist destinations.destinationDescription
der für valueForKeyPath
benötigte Schlüsselpfad. Der Rückgabetyp dieser Methode ist wieder AnyObject
und muss vor der Ausgabe in ein String
-Array umgewandelt werden.
// Sämtliche Ziele ermitteln und ausgeben var allDescriptions = truck.valueForKeyPath( "destinations.destinationDescription") as! [String] for descrip in allDescriptions { print("Ziel: \(descrip)") }
Möchte man explizit die Anzahl der Ziele abfragen, kann dies über die Aggregatfunktion @count
geschehen, die ebenfalls ein KVC-Aufruf ist. Ein direkter Zugriff auf die Eigenschaft count
des Arrays ist hingegen nicht möglich.
// Die Anzahl der Ziele ausgeben print(truck.valueForKeyPath("destinations.@count"))
Benötigt man die Summe aller Entfernungen, muss man die einzelnen Strecken nicht selbst addieren. Key Value Coding verfügt über die Funktion @sum
, die die Summe für uns berechnet.
// Die Summe der einzelnen Entfernungen print(truck.valueForKeyPath("destinations.@sum.distance"))
Die längste und die kürzeste Entfernung in der Auflistung lassen sich auch ohne große Mühe ermitteln, indem man die Funktionen @min
und @max
einsetzt.
// Die kürzeste und die längste Entfernung print(truck.valueForKeyPath("destinations.@min.distance")) print(truck.valueForKeyPath("destinations.@max.distance"))
Wird die durchschnittliche Entfernung benötigt, muss dieser Betrag ebenfalls nicht von uns berechnet werden. Die Aggregatfunktion @avg
kann dies übernehmen.
// Die durchschnittliche Entfernung print(truck.valueForKeyPath("destinations.@avg.distance"))
Eine andere nützliche Funktion ist @distinctUnionOfObjects
, die eine echte oder unechte Teilmenge von eindeutigen und unterscheidbaren Objekten aus einer Auflistung liefert. Obwohl unser LKW mehrfach den Hafen anfährt, wird das Ziel durch die folgenden Befehle nur einmal ausgegeben.
// Liste der Ziele (eindeutig) var distinctDescriptions = truck.valueForKeyPath( "destinations.@distinctUnionOfObjects.destinationDescription") as! [String] for descrip in distinctDescriptions { print("Ziel: \(descrip)") }
Die meisten Entwickler werden Key Value Coding vermutlich nur selten einsetzen, denn die Programmierung mit Eigenschaften und Punktnotation ist einfacher und überschaubarer. Die verschiedenen Aggregatfunktionen sollten Sie aber kennen. Mit ihnen lässt sich eine Menge an Programmcode einsparen. Swift übernimmt bereitwillig oft benötigte Berechnungen von Summen und durchschnittlichen Werten, um die wir uns sonst selbst kümmern müssten.
Aus einer Menge von Daten eine Untermenge herauszufiltern, die bestimmten Kriterien entspricht, ist in der Softwareentwicklung kein ungewöhnliches Anliegen und kann auf viele unterschiedliche Arten realisiert werden. Der einfachste Weg ist eine Schleife, mit der jedes Element der Auflistung einzeln überprüft wird. Dieser Lösungsansatz ist zwar oft langsam, kann aber ohne Mühe in allen Programmiersprachen umgesetzt werden. Eine Besonderheit bei der Entwicklung mit Swift sind hingegen Prädikate, mit deren Hilfe die gleiche Aufgabe viel einfacher und schneller erledigt werden kann. Die Klasse NSPredicate
aus dem Foundation Framework ermöglicht es, aus einer Auflistung gezielt die Objekte auswählen, die den Vorgaben entsprechen.
Um Prädikate und die Klasse NSPredicate
in der Praxis ausprobieren zu können, benötigen wir im Projekt eine Liste mit Daten, die gefiltert werden kann, und wir können in den folgenden Beispielen auf unsere Klasse Truck
zurückgreifen. Erstellen Sie ein Array, wie im folgenden Listing zu sehen, und füllen Sie es mit unterschiedlichen LKW-Typen. Sie können die Liste problemlos um zusätzliche Einträge mit anderen Eigenschaften erweitern. Je mehr Objekte Sie dem Array hinzufügen, desto eindrucksvoller wird das Ergebnis aussehen.
var truckArray = [Truck]() var truck = Truck() truck.make = "Manyroads" truck.color = "Blau" truck.wheels = 4 truckArray.append(truck) truck = Truck() truck.make = "Heavylift" truck.color = "Silber" truck.wheels = 4 truckArray.append(truck) truck = Truck() truck.make = "Manyroads" truck.color = "Schwarz" truck.wheels = 6 truckArray.append(truck) truck = Truck() truck.make = "Drivealot" truck.color = "Rot" truck.wheels = 6 truckArray.append(truck) truck = Truck() truck.make = "Cargoking" truck.color = "Rot" truck.wheels = 6 truckArray.append(truck)
Nachdem die Liste gefüllt wurde, können wir mit der Arbeit beginnen. Zwei LKW in der Liste sind vom Hersteller Manyroads
und unsere erste Aufgabe soll darin bestehen, genau die zwei Fahrzeuge mithilfe eines Prädikats zu ermitteln. Die Definition des NSPredicate
-Objekts geschieht mit einer Zeichenkette und ist in diesem Beispiel nicht kompliziert. Das tatsächliche Filtern wird anschließend über den Aufruf der filter
-Methode des Arrays ausgeführt, die ein neues Array erzeugt, das ausschließlich die gesuchten LKW enthält.
// Ein Predicate für die Marke definieren let nameFilter = NSPredicate(format: "make == 'Manyroads'") // Den Filter anwenden und ein neues Array füllen let filteredTrucks = truckArray.filter {nameFilter.evaluateWithObject($0)} // Das gefilterte Ergebnis ausgeben for singleTruck in filteredTrucks { print("\(singleTruck.make!) \(singleTruck.color!)") }
In manchen Situationen möchte man vielleicht kein neues Array erzeugen, sondern nur prüfen, ob ein Objekt den im Prädikat angegebenen Bedingungen entspricht. Die Methode evaluateWithObject
der Klasse NSPredicate
erlaubt diese Überprüfung. Mit einer Schleife können wir dann leicht kontrollieren, welches Fahrzeug vom Hersteller Manyroads
ist und welches nicht.
let nameFilter = NSPredicate(format: "make == 'Manyroads'") for singleTruck in truckArray { if nameFilter.evaluateWithObject(singleTruck) { print(String(format: "Gesuchtes Fahrzeug: %@ in %@", singleTruck.make!, singleTruck.color! )) } else { print(String(format: "Ein anderes Fahrzeug: %@ in %@", singleTruck.make!, singleTruck.color!)) } }
Die Definition eines Prädikats erinnert an eine Anweisung der Datenbanksprache SQL, weil für die Bedingungen eine ähnliche Syntax verwendet wird. Da es sich um eine Zeichenkette handelt, treten Probleme in der Regel erst zur Laufzeit auf. Der Compiler kann ein Prädikat nicht überprüfen. In manchen Situationen ist ein NSPredicate
jedoch tolerant und erlaubt die Definition eines Vergleichs mit nur einem Gleichheitszeichen. Sie sollten sich diese Schreibweise aber besser gar nicht erst angewöhnen!
let nameFilter = NSPredicate(format: "make = 'Manyroads'")
Möchte man die LKW anhand ihres Herstellers in Gruppen von A bis Z einteilen, kann das über ein Prädikat mit BEGINSWITH
realisiert werden, das nicht den kompletten Namen, sondern den Anfang der Zeichenkette vergleicht. Die Groß- und Kleinschreibung kann über den zusätzlichen Operator [c]
ignoriert werden und Buchstaben mit einem Akzent, wie Ê oder Â, werden durch [d]
korrekt eingeordnet. Benötigten Sie beide Optionen, was vermutlich bei den meisten Textvergleichen der Fall sein wird, können Sie die Operatoren zu einem [cd]
kombinieren.
// Alle Hersteller, die mit einem D oder d beginnen let nameFilter = NSPredicate(format: "make BEGINSWITH [cd] %@","d")
In vielen Beispielen, die zur Erklärung von BEGINSWITH
dienen, wird die angegebene Eigenschaft mit einem einzelnen Zeichen verglichen, obwohl der Vergleich mit mehreren Zeichen problemlos möglich ist. Das Gegenstück zu BEGINSWITH
ist ENDSWITH
und vergleicht die Zeichen am Ende von Strings. Mit CONTAINS
können Sie prüfen, ob sich Zeichen an beliebiger Stelle im Text befinden.
Ähnlich wie bei einer if
-Struktur können in einem Prädikat boolesche Operatoren verwendet werden, wenn NSPredicate
mehrere Bedingungen abbilden soll. Als Ergänzung zu den Operatoren &&
, ||
und !
kann innerhalb des Prädikats mit den Begriffen AND
, OR
und NOT
gearbeitet werden. Das folgende Prädikat filtert sämtliche Objekte, bei denen die Eigenschaft make
ein »y« enthält oder die Farbe des Fahrzeuges Rot ist.
// Hersteller mit einem Y oder mit roter Farbe let nameFilter = NSPredicate(format: "make CONTAINS [c] %@ OR color CONTAINS [c] %@","y", "rot")
Alle LKW vom Hersteller Manyroads
mit sechs oder mehr Reifen liefert die folgende Definition. Ob statt eines AND
ein &&
verwendet wird, hat auf das Suchergebnis keinen Einfluss, denn beide Operatoren arbeiten exakt gleich. Die Programmierung mit AND
und OR
signalisiert allerdings viel besser die Nähe zu SQL. So erhalten wir eine gut sichtbare Trennung zwischen der Logik der Prädikate und der Programmlogik. Indem wir für alle Schlüsselwörter wie CONTAINS
oder BEGINSWITH
ausschließlich Großbuchstaben verwenden, wird das Prädikat besser lesbar.
let nameFilter = NSPredicate( format:"wheels >= 6 && make == 'Manyroads'")
Ist die Bedingung für ein Prädikat umfangreich, ist es oft der einfachste Weg, das NSPredicate
mit einer Closure anzulegen. Weil im zugehörigen Block sämtliche Swift-Anweisungen zur Verfügung stehen, können umfangreiche Bedingungen leicht umgesetzt werden. Im folgenden Listing wird ein Prädikat definiert, das alle Fahrzeuge filtert, die vier oder weniger Reifen haben oder deren Farbe nur drei Zeichen lang ist. Der Parameter evaluatedObject
muss für einen gültigen Vergleich zunächst von AnyObject
wieder in ein Truck
-Objekt umgewandelt werden. Ist die Bedingung erfüllt, gibt die Closure ein true
zurück, im anderen Fall ist der Rückgabewert false
. Der Parameter bindings
wird in dieser Closure nicht benötigt. Wie bei allen Closure können auch für ein Prädikat Variablen aus dem übergeordneten Gültigkeitsbereich eingefangen werden.
// Maximale Anzahl von Reifen let maxWheels = 4 // Maximale Anzahl von Zeichen let maxCharacters = 3 var predicate = NSPredicate { (evaluatedObject, bindings) in if let truck = evaluatedObject as? Truck { if truck.color?.characters.count == maxCharacters || truck.wheels <= maxWheels { return true } } return false } // Den Filter anwenden und ein neues Array füllen let filteredTrucks = truckArray.filter {predicate.evaluateWithObject($0)} // Das gefilterte Ergebnis ausgeben for singleTruck in filteredTrucks { print("\(singleTruck.make!) \(singleTruck.color!)") }
Vielleicht sind Ihnen reguläre Ausdrücke (englisch: Regular Expressions) vertraut, mit denen sich ebenfalls Zeichenketten auf bestimmte Kriterien hin untersuchen lassen. Die zur Definition eines regulären Ausdrucks erforderliche Syntax ist im Vergleich zu Befehlen wie CONTAINS
oder BEGINSWITH
komplizierter, ermöglicht zum Ausgleich aber eine große Flexibilität für mögliche Filter. Die folgenden Listings zeigen einige einfache Anwendungsfälle. Im ersten Beispiel werden alle Fahrzeuge aus dem Array herausgefiltert, deren Farbe mit »S« beginnt. In unserer Liste sind das Silber und Schwarz.
let predicate = NSPredicate(format:"color MATCHES 'S.*' ") // Den Filter anwenden und ein neues Array füllen let filteredTrucks = truckArray.filter {predicate.evaluateWithObject($0)} // Das gefilterte Ergebnis ausgeben for singleTruck in filteredTrucks { print("\(singleTruck.make!) \(singleTruck.color!)") }
Die LKW der Hersteller Divealot
und Heavylift
sind das Ergebnis der folgenden Definition. Ihre Namen enden mit einem kleinen t.
let predicate = NSPredicate(format:"make MATCHES '.*t' ")
Eine Suche nach Zeichen an einer beliebigen Stelle im Text zeigt das folgende Beispiel, bei dem allerdings nur kleine Buchstaben gültig sind. Fahrzeuge der Marke Drivealot
werden nicht ausgegeben.
let predicate = NSPredicate(format:"make MATCHES '.*d.*' ")
Das folgende Prädikat liefert alle Marken, die mit einem großen C beginnen. Das ist in unsere Liste Cargoking
. Ändern Sie das Zeichen zu einem kleinen c, werden keine Übereinstimmungen mehr gefunden.
let predicate = NSPredicate(format:"make MATCHES 'C.*' ")
Alle LKW, deren Markenname mit einem Großbuchstaben aus dem Bereich A bis L beginnt, werden durch die nächste Definition ermittelt. Fahrzeuge von Manyroads
fehlen dann in der Ergebnismenge.
let predicate = NSPredicate(format:"make MATCHES '[A-L].*' ")
Reguläre Ausdrücke können sehr komplex werden, und es ist an dieser Stelle leider nicht möglich, dieses Thema umfassend zu behandeln. Falls Ihnen reguläre Ausdrücke gefallen, werden Sie im Internet ohne Mühe reichhaltige Informationen finden. Einige Fachbücher, manche davon mit beachtlichem Umfang, haben sich ebenfalls dieser Thematik angenommen.
Nach viel Theorie wollen wir uns jetzt einer möglichen Verwendung in einer iOS-App zuwenden. Ein einfacher Weg, die Filter- und Suchfunktionen von Swift in der Praxis auszuprobieren, lässt sich mit einem UITableView
-Steuerelement realisieren. Wenn die Anwendung startet oder noch kein Suchkriterium zugewiesen wurde, zeigt die Tabelle alle Objekte ihrer Datenquelle an. Nach der Eingabe eines Filters beschränkt sich die Ausgabe auf die übereinstimmenden Elemente. Im Daten-Model müssen wir jetzt zwei Listen verwalten. Eine Liste mit allen Objekten und eine gefilterte Liste, die nur jene Objekte enthält, die auf der grafischen Oberfläche angezeigt werden sollen. Leicht lassen sich die Daten mit einer Repository-Klasse verwalten. Sie enthält neben den auszugebenden Daten-Objekten zusätzliche Methoden, um die Ausgabe zu filtern und zu sortieren. Für die Eingabe der Suchkriterien gibt es im UIKit Framework sogar ein spezielles Eingabefeld-Steuerelement, das direkt oberhalb eines UITableView
angedockt werden kann. Die Einstellungen
-App von Apple zeigt gut, wie die grafische Oberfläche einer solchen Anwendung aufgebaut wird.
In einer leicht verständlichen App wollen wir eine Liste von Städtenamen filtern und sortieren, und das Projekt soll CityList
heißen. Für die Vorlage verwenden wir den Projekttyp Single View Application. Als Erstes müssen wir den View Controller im Storyboard austauschen. Löschen Sie die aktuelle Ansicht und ziehen Sie anschließend die Vorlage Table View Controller aus der Object Library in den Arbeitsbereich. Damit das Storyboard mit dieser Szene startet, muss im Attributes Inspector die Eigenschaft Is Initial View Controller gesetzt werden. Sobald der große Pfeil auf die Szene zeigt, ist alles in Ordnung. Mit einer Warnung verlangt die Entwicklungsumgebung nach einem Identifier für die Prototypen-Zelle im Table View. Nennen Sie die Zelle Cell
.
Wenn Sie das Projekt kompilieren und ausführen, werden Sie im Simulator ein weißes Fenster mit den vertrauten grauen Trennlinien eines Tabellen-Steuerelements sehen. Jetzt können wir mit der eigentlichen Arbeit beginnen.
Wie zuvor angedeutet, werden wir in diesem Projekt auf ein komplexes Daten-Model verzichten und in unserer Repository-Klasse lediglich Auflistungen von Zeichenketten mit Städtenamen verwalten. Erstellen Sie zunächst eine neue Klasse mit dem Namen CityRepository
und fügen Sie in der Implementierung Eigenschaften für zwei Arrays hinzu. cityArray
ist die unveränderliche Auflistung aller Städtenamen und wird bei der Initialisierung der Klasse mit Inhalt gefüllt. Die gefilterte Liste filteredCityArray
ist eine Variable, da sich ihr Inhalt zur Laufzeit, abhängig von den Benutzereingaben, ändern soll. Würde man nach einem Namen suchen, der in der Liste aller Städte nicht zu finden ist, wäre die gefilterte Liste leer.
import Foundation class CityRepository { // Die Liste aller Städte let citiesArray:[String] // Die gefilterte Liste var filteredCitiesArray:[String] }
Die im nächsten Listing gezeigte Liste mit Städtenamen können Sie nach Ihren Wünschen ändern und erweitern. Je mehr Objekte dem Array hinzugefügt werden, desto eindrucksvoller ist das Programm. Die Methode resetFilter
überträgt sämtliche Objekte aus dem citiesArray
in das filteredCitiesArray
und stellt somit alle Namen zur Anzeige zur Verfügung. Diese Methode benötigen wir beim Start der App und nachdem der Anwender einen zuvor eingetragenen Filter entfernt hat. Wir müssen uns nicht die Mühe machen, die Orte im Code anhand ihrer Namen zu sortieren. Diese Aufgabe übernimmt das Programm durch den Aufruf sortInPlace
.
init() { citiesArray = ["London", "Berlin", "Paris", "Wien", "Istanbul", "Dallas", "Stockholm", "New York", "Hong Kong", "Bern", "Athen", "Kiew", "Frankfurt", "Buenos Aires"] filteredCitiesArray = [String]() self.resetFilter() } func resetFilter() { // Eventuell vorhandene Objekte entfernen self.filteredCitiesArray.removeAll() // Alle Elemente aus dem Array citiesArray // in das Array filteredCitiesArray übertragen for city in citiesArray { filteredCitiesArray.append(city) } // Das Array sortieren filteredCitiesArray.sortInPlace( {$0.caseInsensitiveCompare($1) == NSComparisonResult.OrderedAscending }) }
Die Methode zur Filterung der Städtenamen soll filter
heißen und ist vom Aufbau her ähnlich der Methode resetFilter
. Erneut kommt eine for
-Schleife zum Einsatz, um die Objekte in die Liste der gefilterten Städte zu übertragen. Durch ein zuvor definiertes Prädikat wird zunächst geprüft, ob die als Parameter übergebene Zeichenkette im Namen einer Stadt zu finden ist. Statt eines Vergleichs mit CONTAINS
könnte mit BEGINSWITH
verglichen werden, woraufhin nur noch am Anfang des Namens nach Übereinstimmungen gesucht wird. Nachdem das Array gefüllt wurde, kann das gefilterte Ergebnis mit sortInPlace
sortiert werden.
func filter(searchString:String) { let predicate = NSPredicate( format: "self CONTAINS [cd] %@", searchString) self.filteredCitiesArray.removeAll() // Alle Elemente aus dem Array citiesArray // in das Array filteredCitiesArray übertragen for city in citiesArray { if predicate.evaluateWithObject(city) { filteredCitiesArray.append(city) } } filteredCitiesArray.sortInPlace({ $0.caseInsensitiveCompare($1) == NSComparisonResult.OrderedAscending }) }
Die zur Kommunikation zwischen Repository und Tabellen-View-Controller benötigten Methoden können wir mit kleinen Änderungen bei bekannten Beispielen übernehmen. itemsCount
liefert die Anzahl der Elemente in der gefilterten Liste und itemAtIndex
immer genau ein Objekt an der angegebenen Position.
func itemsCount() -> Int { return self.filteredCitiesArray.count } func itemAtIndex(index:Int) -> String { return self.filteredCitiesArray[index] }
Nachdem die Arbeit an unserem Repository abgeschlossen ist, sollten wir die Anwendung jetzt so weit vorbereiten, dass zumindest die Liste aller Städte schon auf der grafischen Oberfläche angezeigt wird. Eine von UITableViewController
abgeleitete Klasse mit dem Namen CityTableViewController
soll der View Controller der Szene werden und unser CityRepository
wird innerhalb der Klasse eine Eigenschaft.
import UIKit class CityTableViewController: UITableViewController { var cityRepository:CityRepository? override func viewDidLoad() { cityRepository = CityRepository() super.viewDidLoad() } ...
Die von Xcode für den UITableView
vorbereiteten Methoden können wir nach dem bekannten Muster mit Leben füllen. Unsere Liste hat nur eine Sektion und die Anzahl der Zeilen entspricht der Anzahl der Zeichenketten im Array. In der Methode tableView(_: cellForRowAtIndexPath:)
holen wir uns einen Eintrag aus unserem Repository und weisen den Namen der Stadt dem textLabel
der Tabellenzelle zu.
override func numberOfSectionsInTableView( tableView: UITableView) -> Int {// Nur eine Sektion für die Tabelle
return 1
} override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {// Die Anzahl der Zeilen entspricht der Anzahl
// der Objekte im Repository
return self.cityRepository!.itemsCount()
} override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
// Ein Objekt aus dem Repository holen und der
// Zelle zuweisen
let city = self.cityRepository!.itemAtIndex(indexPath.row)
cell.textLabel?.text = city
return cell }
Vor einem ersten Test müssen wir die Identität der View-Controller-Klasse im Storyboaod anpassen. Eine Aufgabe für den Identity Inspector. Markieren Sie den orangefarbenen View-Controller-Platzhalter im Dock der Szene oder in der Seitenleiste und ändern Sie die Eigenschaft Class in unsere Klasse CityTableViewController
.
Die Verbindungen für Delegate und DataSource hat die Entwicklungsumgebung für uns angelegt, trotzdem sollten wir uns einen Augenblick Zeit nehmen und die Einstellungen im Connections Inspector kontrollieren. Reagiert eine Tabelle nicht auf Aktionen des Benutzers oder zeigt sie keine Daten, kann dort der Fehler liegen. In älteren Versionen von Xcode mussten Entwickler die Verbindungen selbst anlegen. Die grafische Oberfläche, der View, wurde ebenfalls von Xcode mit der Eigenschaft view
der Controller-Klasse verbunden.
Jetzt können wir endlich die App ausprobieren, und wie geplant zeigt uns der Tabellen-View-Controller eine sortierte Liste aller Städtenamen, die wir im Array hinterlegt haben.
Soll in einer App gesucht werden, ist seit iOS 8 die Klasse UISearchController
der richtige Ansprechpartner. Er übernimmt die Aufgabe, die Suchleiste über der Tabelle einzublenden, die Sie vermutlich aus anderen Apps kennen. Leider kann das Steuerelement in der aktuellen Xcode-Version nicht mit dem Interface Builder eingefügt werden. Wir müssen stattdessen eine Instanz im Programmcode erzeugen. Im ersten Schritt erhält unsere Klasse CityTableView
deshalb eine Eigenschaft vom Typ UISearchController
. Die Konfiguration eines neuen Objekts folgt in viewDidLoad
. Für eine reibungslose Kommunikation zwischen dem UISearchController
und unserer Controller-Klasse sorgt das UISearchResultsUpdating
-Protokoll.
class CityTableViewController: UITableViewController,UISearchResultsUpdating
{ var cityRepository:CityRepository?var searchController:UISearchController?
override func viewDidLoad() { self.cityRepository = CityRepository()self.searchController
= UISearchController(searchResultsController: nil)
self.searchController!.searchResultsUpdater
= self
self.searchController!.dimsBackgroundDuringPresentation
= false
self.definesPresentationContext = true
self.tableView.tableHeaderView
= searchController?.searchBar
super.viewDidLoad() } ...
Die Konfiguration eines UISearchController
ist zwar umfangreich, aber nicht schwierig. Bei der Initialisierung wird der Instanz für den Parameter searchResultsController
der Wert nil
übergeben, was zur Folge hat, dass wir die aktuelle Ansicht auch zur Anzeige der Suchergebnisse verwenden müssen. Alternativ könnte ein anderer Controller übergeben werden. Mit der Eigenschaft searchResultsUpdater
wird bestimmt, welches Objekt Benachrichtigungen enthält, wenn der Nutzer in die Suchleiste schreibt. In unserem Fall ist es die Controller-Klasse CityTableViewController
, also self
. Möglich wäre jede beliebige Klasse, die das UISearchResultsUpdating
-Protokoll implementiert. Der Wert true
für die Eigenschaft definesPresentationContext
verhindert, dass die Suchleiste sichtbar bleibt, wenn der Nutzer zu einer anderen Szene navigiert. Mit der Eigenschaft dimsBackgroundDuringPresentation
wird festgelegt, ob der aktuelle View während einer Eingabe in die Suchleiste abgedunkelt wird.
Zur Umsetzung des UISearchResultsUpdating
-Protokolls müssen wir unsere Controller-Klasse um eine Methode erweitern. updateSearchResultsForSearchController
wird aufgerufen, wenn der Nutzer in die Suchleiste schreibt oder wenn die Suche durch die Schaltfläche Cancel abgebrochen wurde. Abhängig von der Anzahl der eingegebenen Zeichen können wir dann entscheiden, ob der Tabelleninhalt gefiltert wird oder nicht. Immer erforderlich ist der Aufruf von reloadData
. Mit ihm wird die Tabelle aufgefordert, ihre Daten neu zu laden. Viel mehr müssen wir nicht tun. Die Logik zum Filtern haben wir zuvor in unserem Repository implementiert.
func updateSearchResultsForSearchController( searchController: UISearchController) { // Der in die Suchleiste eingegebene Text let searchText = searchController.searchBar.text! if searchText.characters.count > 0 { // Inhalt filtern self.cityRepository!.filter(searchText) } else { // Filter zurücksetzen self.cityRepository!.resetFilter() } self.tableView.reloadData() }
Jetzt funktioniert unser Programm wie geplant, allerdings sind noch einige Schönheitskorrekturen anzuraten. Die Statusleiste überdeckt die Suchleiste und macht die App unansehnlich. Besser wäre es, die Statusleiste in der App komplett auszublenden und mit zwei Einträgen in der Info.plist
ist das tatsächlich möglich. Erweitern Sie die Datei um den Key UIStatusBarHidden
mit dem Wert true
und um UIViewControllerBasedStatusBarAppearance
mit dem Key false
.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict><key>UIStatusBarHidden</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
...
Zusätzliche Anpassungen können in der viewDidLoad
-Methode der View-Controller-Klasse vorgenommen werden. Beispielsweise die Farben der Suchleiste und der Platzhaltertext für das Eingabefeld.
let backgroundColor = UIColor(colorLiteralRed: 0.7, green: 0.7, blue: 0.8, alpha: 1.0) let tintColor = UIColor(colorLiteralRed: 1.0, green: 0.0, blue: 0.0, alpha: 1.0) // Hintergrundfarbe der Suchleiste self.searchController?.searchBar.barTintColor = backgroundColor // Buttontextfarbe und Cursorfarbe self.searchController?.searchBar.tintColor = tintColor // Platzhaltertext für die Suchleiste self.searchController?.searchBar.placeholder = "Suchen ..."
Wollte man aus dem Suchergebnis in eine Detailansicht wechseln, wäre das ohne Probleme möglich. Methoden wie tableView(_: didSelectRowAtIndexPath:)
stehen im CityTableViewController
weiterhin zur Verfügung. Der indexPath
bezieht sich im kompletten Tabellen-View-Controller immer auf einen Eintrag aus dem filteredCitiesArray
der Repository-Klasse. Ob die Auflistung mit allen Städtenamen gefüllt ist oder einen Teil enthält, wird über die Filtermethode gesteuert. Versuchen Sie, das Projekt eigenständig um eine Detailansicht zu erweitern.
// Variablen und Konstanten var variableValue = 3.1415 variableValue = 3.0 let constValue = 42 // Neue Zuweisung nicht möglich // constValue = 50 Fallunterscheidungen var salary = 10000 if salary > 15000 { print("Zu viel") } else if salary < 10000 { print("Zu wenig") } else { print("Ok") } var index = 4 switch index { case 1: print("Index ist 1") case 2: print("Index ist 2") case 3...5: print("Index liegt zwischen 3 und 5") case 6: print("Index ist 6") fallthrough default: print("Index liegt nicht zwischen 1 bis 5") }
for var index = 0; index <= 5; index++ { print(index) // Ausgabe von 0 bis 5 } for index in 0...5 // Closed-Range { print(index) // Ausgabe von 0 bis 5 } for index in 0..<5 // Half-Closed-Range { print(index) // Ausgabe von 0 bis 4 } for index in (5...10).reverse() { print(index) // Ausgabe von 10 bis 5 } var access = 1 repeat { print(access) // Ausgabe von 1 bis 9 access++ } while access < 10
// Ohne Parameter und ohne Rückgabewert func doSomething() { print("Hello World!") } doSomething() // Mit einem Double als Parameter und einem Double // als Rückgabewert func doSomething(value:Double) -> Double { return value * 2.0 } var twice = doSomething(4.5) // Mit zwei benannten Parametern // und einem Double als Rückgabewert func doSomething( firstValue first:Double, secondValue second:Double) -> Double { return first + second } var sum = doSomething(firstValue: 3.2, secondValue: 4.7)
var cities:[String] = ["Rom","Paris","Berlin"] cities.append("London") for city in cities { print(city) // Ausgabe alle Städtenamen } var count = cities.count // Anzahl der Elemente
var animals:[Int: String] = [11:"Hund", 22:"Katze", 33:"Maus"] animals[22] = "Elefant" // Element ersetzen animals[99] = "Huhn" // Element anfügen print(animals[22]) // Ausgabe "Optional (Elefant)" print(animals[99]) // Ausgabe "Optional (Huhn)" animals[11] = nil // Element entfernen
var firstName = "Mike" var lastName = "Müller" // String Interpolation print("Mein Name ist \(firstName) \(lastName).") // Zahlenwerte formatieren var numeric = 3.34983632 var num = String(format:"%.2f", numeric) // Ausgabe 3.35
enum Color { case Red case Green case Blue } var exterior:Color = Color.Green Klassen class Person { // Eigenschaften var firstName:String var lastName:String init() { // Eigenschaften initialisieren firstName = "" lastName = "" } } // Klasseninstanz erzeugen und // Eigenschaften zuweisen var pers = Person() pers.firstName = "Mike" pers.lastName = "Müller"
// Eigene Fehlertypen definieren enum CalculationError : ErrorType { case DivideByZero case OtherError } // Funktion kann einen Fehler zurückgeben func divide(dividend:Int, divisor:Int) throws -> Int { guard divisor > 0 else { throw CalculationError.DivideByZero } return dividend / divisor } // Funktionsaufruf und Fehlerbehandlung do { var result = try divide(100, divisor: 0) } catch(CalculationError.DivideByZero) { print("Es wurde versucht, durch null zu teilen") } catch { print("Ein anderer Fehler ist aufgetreten") }