German (Deutsch) translation by Władysław Łucyszyn (you can also view the original English article)
Im vorherigen Lernprogramm haben wir die Möglichkeit hinzugefügt, Aufgaben zu erstellen. Während dieser Zusatz die Anwendung ein wenig nützlicher gemacht hat, wäre es auch bequem, die Möglichkeit hinzuzufügen, Elemente als erledigt zu markieren und Elemente zu löschen. Darauf konzentrieren wir uns in diesem Tutorial.
Voraussetzungen
Wenn Sie mir folgen möchten, stellen Sie sicher, dass Xcode 6.3 oder höher auf Ihrem Computer installiert ist. Zum Zeitpunkt der Erstellung dieses Artikels befindet sich Xcode 6.3 in der Betaversion und ist im Apple iOS Dev Center für registrierte iOS-Entwickler erhältlich.
Der Grund für die Anforderung von Xcode 6.3 oder höher besteht darin, Swift 1.2 nutzen zu können, das Apple im Februar eingeführt hat. Swift 1.2 führt eine Reihe von großartigen Ergänzungen ein, die wir im Rest dieser Serie nutzen werden.
1. Löschen von Artikeln
Um Elemente zu löschen, müssen zwei zusätzliche Methoden des UITableViewDataSource
-Protokolls implementiert werden. Zuerst müssen wir der Tabellenansicht mitteilen, welche Zeilen bearbeitet werden können, indem wir die Methode tableView(_: canEditRowAtIndexPath:)
implementieren. Wie Sie im folgenden Codeausschnitt sehen können, ist die Implementierung unkompliziert. Wir sagen der Tabellenansicht, dass jede Zeile editierbar ist, indem sie true
zurückgibt.
1 |
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { |
2 |
return true |
3 |
}
|
Die zweite Methode, an der wir interessiert sind, ist tableView(_:commitEditingStyle:forRowAtIndexPath:)
. Die Implementierung ist ein wenig komplexer, aber einfach genug zu erfassen.
1 |
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { |
2 |
if editingStyle == .Delete { |
3 |
// Fetch Item
|
4 |
let item = self.items[indexPath.row] |
5 |
|
6 |
// Update Items
|
7 |
self.items.removeAtIndex(indexPath.row) |
8 |
|
9 |
// Update Table View
|
10 |
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Right) |
11 |
}
|
12 |
}
|
Wir beginnen mit der Überprüfung des Werts von editingStyle
, einer Enumeration vom Typ UITableViewCellEditingStyle
. Wir löschen nur ein Element, wenn der Wert von editingStyle
gleich UITableViewCellEditingStyle.Delete
ist.
Swift ist jedoch schlauer. Da es weiß, dass editingStyle
vom Typ UITableViewCellEditingStyle
ist, können wir UITableViewCellEditingStyle
, den Namen der Enumeration und write .Delete
, den Member-Wert der Enumeration, an der wir interessiert sind, weglassen. Wenn Sie mit Aufzählungen in Swift noch nicht vertraut sind, empfehle ich Ihnen, diesen Kurztipp zu Aufzählungen in Swift zu lesen.
Als Nächstes holen wir das entsprechende Element aus der Eigenschaft items
und speichern dessen Wert vorübergehend in einer Konstanten namens item
. Wir aktualisieren die Datenquelle der Tabelle views, items
, indem wir removeAtIndex(index:Int)
für die Eigenschaft items
aufrufen und den richtigen Index übergeben.
Schließlich aktualisieren wir die Tabellenansicht, indem wir deleteRowsAtIndexPaths(_:withRowAnimation:)
auf tableView
aufrufen und ein Array mit indexPath
und .Right
übergeben, um den Animationstyp anzugeben. Wie wir bereits gesehen haben, können wir den Namen der Enumeration, UITableViewRowAnimation
, weglassen, da Swift weiß, dass der Typ des zweiten Arguments UITableViewRowAnimation
ist.
Der Benutzer sollte nun in der Lage sein, Einträge aus der Liste zu löschen. Erstellen und führen Sie die Anwendung aus, um dies zu testen.
2. Auschecken von Artikeln
Um ein Element als erledigt zu markieren, fügen wir der entsprechenden Zeile ein Häkchen hinzu. Dies bedeutet, dass wir die Punkte, die der Benutzer als erledigt markiert hat, im Auge behalten müssen. Zu diesem Zweck werden wir eine neue Eigenschaft deklarieren, die dies für uns verwaltet. Deklarieren Sie eine Variableneigenschaft, checkedItems
, vom Typ [String]
und initialisieren Sie sie mit einem leeren Array.
1 |
var checkedItems: [String] = [] |
In tableView(_:cellForRowAtIndexPath:)
überprüfen wir, ob checkedItems
das entsprechende Element enthält, indem wir die Funktion contains
verwenden, eine globale Funktion, die in der Swift Standard Library definiert ist. Wir übergeben checkedItems
als erstes Argument und item
Element als zweites Argument. Die Funktion gibt true
zurück, wenn checkedItems
item
enthält.
1 |
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { |
2 |
// Fetch Item
|
3 |
let item = self.items[indexPath.row] |
4 |
|
5 |
// Dequeue Table View Cell
|
6 |
let tableViewCell = tableView.dequeueReusableCellWithIdentifier("TableViewCell", forIndexPath: indexPath) as! UITableViewCell |
7 |
|
8 |
// Configure Table View Cell
|
9 |
tableViewCell.textLabel?.text = item |
10 |
|
11 |
if contains(self.checkedItems, item) { |
12 |
tableViewCell.accessoryType = .Checkmark |
13 |
} else { |
14 |
tableViewCell.accessoryType = .None |
15 |
}
|
16 |
|
17 |
return tableViewCell |
18 |
}
|
Wenn item
in checkedItems
gefunden wird, setzen wir die accessoryType
-Eigenschaft der Zelle auf .Checkmark
, einen Memberwert der UITableViewCellAccessoryType
-Enumeration. Wenn der item
nicht gefunden wird, greifen wir zurück auf .None
als Zubehörtyp der Zelle.
Der nächste Schritt besteht darin, die Möglichkeit hinzuzufügen, ein Element als erledigt zu markieren, indem eine Methode des UITableViewDelegate
-Protokolls, tableView(_:didSelectRowAtIndexPath:)
, implementiert wird. In dieser Delegate-Methode rufen wir zuerst deselectRowAtIndexPath(_:animated:)
in tableView
auf, um die Zeile aufzuheben, die der Benutzer angetippt hat.
1 |
// MARK: Table View Delegate Methods
|
2 |
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { |
3 |
tableView.deselectRowAtIndexPath(indexPath, animated: true) |
4 |
|
5 |
// Fetch Item
|
6 |
let item = self.items[indexPath.row] |
7 |
|
8 |
// Fetch Table View Cell
|
9 |
let tableViewCell = tableView.cellForRowAtIndexPath(indexPath) |
10 |
|
11 |
// Find Index of Item
|
12 |
let index = find(self.checkedItems, item) |
13 |
|
14 |
if let index = index { |
15 |
self.checkedItems.removeAtIndex(index) |
16 |
tableViewCell?.accessoryType = UITableViewCellAccessoryType.None |
17 |
|
18 |
} else { |
19 |
self.checkedItems.append(item) |
20 |
tableViewCell?.accessoryType = UITableViewCellAccessoryType.Checkmark |
21 |
}
|
22 |
}
|
Wir holen dann das entsprechende Element aus den item
Elementen und einen Verweis auf die Zelle, die der angezapften Zeile entspricht. Wir verwenden die find
funktion, die in der Swift Standard Library definiert ist, um den Index des item
Elements in checkedItems
zu erhalten. Die find
Funktion find gibt optional Int
zurück. Wenn checkedItems
item
enthält, entfernen wir es aus checkedItems
und setzen den Zubehörtyp der Zelle auf .None
. Wenn checkedItems
kein item
Element enthält, fügen wir es zu checkedItems
hinzu und setzen den Zubehörtyp der Zelle auf .Checkmark
.
Mit diesen Hinzufügungen kann der Benutzer Artikel als erledigt markieren. Erstellen und führen Sie die Anwendung aus, um sicherzustellen, dass alles wie erwartet funktioniert.
3. Speicherstatus
Die Anwendung speichert den Status zwischen den Starts derzeit nicht. Um dies zu beheben, speichern wir die items
Elemente und checkedItems
-Arrays in der Benutzerstandarddatenbank der Anwendung.
Schritt 1: Ladezustand
Beginnen Sie mit dem Erstellen der zwei Hilfsmethoden loadItems
und loadCheckedItems
. Beachten Sie das private
Schlüsselwort, das jeder Hilfsmethode vorangestellt ist. Das private
Schlüsselwort sagt Swift, dass diese Methoden nur innerhalb dieser Quelldatei zugänglich sind.
1 |
// MARK: Private Helpers
|
2 |
private func loadItems() { |
3 |
let userDefaults = NSUserDefaults.standardUserDefaults() |
4 |
|
5 |
if let items = userDefaults.objectForKey("items") as? [String] { |
6 |
self.items = items |
7 |
}
|
8 |
}
|
9 |
|
10 |
private func loadCheckedItems() { |
11 |
let userDefaults = NSUserDefaults.standardUserDefaults() |
12 |
|
13 |
if let checkedItems = userDefaults.objectForKey("checkedItems") as? [String] { |
14 |
self.checkedItems = checkedItems |
15 |
}
|
16 |
}
|
Das private
Schlüsselwort ist Teil der Zugriffskontrolle von Swift. Wie der Name schon sagt, definiert die Zugriffskontrolle, welcher Code auf welchen Code zugreifen darf. Zugriffsebenen gelten für Methoden, Funktionen, Typen usw. Apple bezieht sich einfach auf Entitäten. Es gibt drei Zugriffsebenen, öffentlich, intern und privat.
- Öffentlich: Entitäten, die als öffentlich gekennzeichnet sind, sind für Entitäten zugänglich, die im selben Modul sowie in anderen Modulen definiert sind. Diese Zugriffsebene ist ideal zum Freilegen der Schnittstelle eines Frameworks.
- Intern: Dies ist die Standardzugriffsebene. Mit anderen Worten, wenn keine Zugriffsebene angegeben ist, gilt diese Zugriffsebene. Eine Entität mit einer Zugriffsebene von intern ist nur für Entitäten zugänglich, die in demselben Modul definiert sind.
- Privat: Auf eine Entität, die als privat deklariert ist, können nur Entitäten zugreifen, die in derselben Quelldatei definiert sind. Beispielsweise sind die in der
ViewController
-Klasse definierten privaten Hilfsmethoden nur für dieViewController
-Klasse verfügbar.
Die Implementierung der Hilfsmethoden ist einfach, wenn Sie mit der NSUserDefaults
-Klasse vertraut sind. Zur einfacheren Verwendung speichern wir einen Verweis auf das Standardbenutzerstandardobjekt in einer Konstanten mit dem Namen userDefaults
. Im Fall von loadItems
fragen wir userDefaults
nach dem Objekt, das mit dem Schlüssel "items"
verknüpft ist und werfen es in ein optionales Array von Strings ab. Wir entfernen das optionale optional, was bedeutet, dass wir den Wert in den Konstantenelementen items
speichern, wenn das Optionale nicht gleich null
ist, und den Wert der Elementeigenschaft items
zuweisen.
Wenn die if
-Anweisung verwirrend aussieht, sehen Sie sich im folgenden Beispiel eine einfachere Version der loadItems
-Methode an. Das Ergebnis ist identisch, der einzige Unterschied ist Prägnanz.
1 |
private func loadItems() { |
2 |
let userDefaults = NSUserDefaults.standardUserDefaults() |
3 |
let storedItems = userDefaults.objectForKey("items") as? [String] |
4 |
|
5 |
if let items = storedItems { |
6 |
self.items = items |
7 |
}
|
8 |
}
|
Die Implementierung von loadCheckedItems
ist identisch mit Ausnahme des Schlüssels, der zum Laden des in der Benutzerstandarddatenbank gespeicherten Objekts verwendet wird. Lassen Sie uns loadItems
und loadCheckedItems
verwenden, indem Sie die viewDidLoad
-Methode aktualisieren.
1 |
override func viewDidLoad() { |
2 |
super.viewDidLoad() |
3 |
|
4 |
// Set Title
|
5 |
self.title = "To Do" |
6 |
|
7 |
// Load State
|
8 |
self.loadItems() |
9 |
self.loadCheckedItems() |
10 |
|
11 |
// Register Class for Cell Reuse
|
12 |
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "TableViewCell") |
13 |
}
|
Schritt 2: Speichern des Status
Um den Status zu speichern, implementieren wir zwei weitere private Hilfsmethoden, saveItems
und saveCheckedItems
. Die Logik ähnelt der von loadItems
und loadCheckedItems
. Der Unterschied besteht darin, dass wir Daten in der Benutzerstandarddatenbank speichern. Stellen Sie sicher, dass die Schlüssel, die in den Aufrufen von setObject(_:forKey:)
verwendet werden, mit den in loadItems
und loadCheckedItems
verwendeten Schlüsseln übereinstimmen.
1 |
private func saveItems() { |
2 |
let userDefaults = NSUserDefaults.standardUserDefaults() |
3 |
|
4 |
// Update User Defaults
|
5 |
userDefaults.setObject(self.items, forKey: "items") |
6 |
userDefaults.synchronize() |
7 |
}
|
8 |
|
9 |
private func saveCheckedItems() { |
10 |
let userDefaults = NSUserDefaults.standardUserDefaults() |
11 |
|
12 |
// Update User Defaults
|
13 |
userDefaults.setObject(self.checkedItems, forKey: "checkedItems") |
14 |
userDefaults.synchronize() |
15 |
}
|
Der synchronize
Aufruf ist nicht unbedingt erforderlich. Das Betriebssystem wird sicherstellen, dass die Daten, die Sie in der Benutzerstandarddatenbank speichern, irgendwann auf die Festplatte geschrieben werden. Durch das Aufrufen der synchronize
weisen Sie das Betriebssystem jedoch ausdrücklich an, alle ausstehenden Änderungen auf den Datenträger zu schreiben. Dies ist während der Entwicklung nützlich, da das Betriebssystem Ihre Änderungen nicht auf die Festplatte schreibt, wenn Sie die Anwendung beenden. Es kann dann so aussehen, als würde etwas nicht richtig funktionieren.
Wir müssen saveItems
und saveCheckedItems
an verschiedenen Stellen aufrufen. Um zu beginnen, rufen Sie saveItems
auf, wenn ein neues Element zur Liste hinzugefügt wird. Wir tun dies in der Delegate-Methode des AddItemViewControllerDelegate
-Protokolls.
1 |
// MARK: Add Item View Controller Delegate Methods
|
2 |
func controller(controller: AddItemViewController, didAddItem: String) { |
3 |
// Update Data Source
|
4 |
self.items.append(didAddItem) |
5 |
|
6 |
// Save State
|
7 |
self.saveItems() |
8 |
|
9 |
// Reload Table View
|
10 |
self.tableView.reloadData() |
11 |
|
12 |
// Dismiss Add Item View Controller
|
13 |
self.dismissViewControllerAnimated(true, completion: nil) |
14 |
}
|
Wenn sich der Status eines Elements in der tableView(_: didSelectRowAtIndexPath:)
ändert, aktualisieren wir checkedItems
. Es ist eine gute Idee, zu diesem Zeitpunkt auch saveCheckedItems
aufzurufen.
1 |
// MARK: Table View Delegate Methods
|
2 |
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { |
3 |
tableView.deselectRowAtIndexPath(indexPath, animated: true) |
4 |
|
5 |
// Fetch Item
|
6 |
let item = self.items[indexPath.row] |
7 |
|
8 |
// Fetch Table View Cell
|
9 |
let tableViewCell = tableView.cellForRowAtIndexPath(indexPath) |
10 |
|
11 |
// Find Index of Item
|
12 |
let index = find(self.checkedItems, item) |
13 |
|
14 |
if let index = index { |
15 |
self.checkedItems.removeAtIndex(index) |
16 |
tableViewCell?.accessoryType = UITableViewCellAccessoryType.None |
17 |
|
18 |
} else { |
19 |
self.checkedItems.append(item) |
20 |
tableViewCell?.accessoryType = UITableViewCellAccessoryType.Checkmark |
21 |
}
|
22 |
|
23 |
// Save State
|
24 |
self.saveCheckedItems() |
25 |
}
|
Wenn ein Element gelöscht wird, werden beide items
und checkedItems
aktualisiert. Um diese Änderung zu speichern, rufen wir saveItems
und saveCheckedItems
auf.
1 |
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { |
2 |
if editingStyle == .Delete { |
3 |
// Fetch Item
|
4 |
let item = self.items[indexPath.row] |
5 |
|
6 |
// Update Items
|
7 |
self.items.removeAtIndex(indexPath.row) |
8 |
|
9 |
if contains(self.checkedItems, item) { |
10 |
self.checkedItems.removeAtIndex(indexPath.row) |
11 |
}
|
12 |
|
13 |
// Save State
|
14 |
self.saveItems() |
15 |
self.saveCheckedItems() |
16 |
|
17 |
// Update Table View
|
18 |
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Right) |
19 |
}
|
20 |
}
|
Das ist es. Erstellen und führen Sie die Anwendung aus, um Ihre Arbeit zu testen. Spielen Sie mit der Anwendung und zwingen Sie es zu beenden. Wenn Sie die Anwendung erneut starten, sollte der letzte bekannte Status geladen und sichtbar sein.
4. Objektbeobachter
Die Benutzererfahrung der Anwendung fehlt momentan etwas. Wenn jedes Element gelöscht wird oder wenn die Anwendung zum ersten Mal gestartet wird, sieht der Benutzer eine leere Tabellenansicht. Das ist nicht großartig. Wir können dies lösen, indem wir eine Nachricht anzeigen, wenn keine Elemente vorhanden sind. Dies gibt mir auch die Möglichkeit, Ihnen ein weiteres Feature von Swift, den Immobilienbeobachtern, zu zeigen.
Schritt 1: Hinzufügen eines Etiketts
Beginnen wir mit dem Hinzufügen eines Labels zur Benutzeroberfläche, um die Nachricht anzuzeigen. Deklarieren Sie in der ViewController
-Klasse ein outlet mit dem Namen messageLabel
vom Typ UILabel
, öffnen Sie Main.storyboard und fügen Sie der Ansicht des View-Controllers eine Beschriftung hinzu.
1 |
@IBOutlet var messageLabel: UILabel! |
Fügen Sie dem Label die erforderlichen Layouteinschränkungen hinzu und verbinden Sie es mit dem MessageLabel
-Ausgang des View-Controllers im Inspektor Verbindungen. Legen Sie den Text des Labels auf Sie haben keine Aufgaben. zentrieren Sie den Label-Text im Attribute Inspector.



Schritt 2: Implementieren eines Objektbeobachters
Das Nachrichtenlabel sollte nur sichtbar sein, wenn items
keine Elemente enthalten. Wenn das passiert, sollten wir auch die Tabellenansicht ausblenden. Wir könnten dieses Problem lösen, indem wir verschiedene Checks in der ViewController
-Klasse hinzufügen, aber ein bequemerer und eleganterer Ansatz besteht darin, einen Property-Observer zu verwenden.
Wie der Name schon sagt, beobachten Property-Beobachter eine Eigenschaft. Eine Eigenschaftenbeobachtung wird immer dann aufgerufen, wenn sich eine Eigenschaft ändert, selbst wenn der neue Wert dem alten Wert entspricht. Es gibt zwei Arten von Immobilienbeobachtern.
-
willSet:
wird aufgerufen, bevor der Wert geändert wurde -
didSet:
wird aufgerufen, nachdem sich der Wert geändert hat
Zu unserem Zweck werden wir den Beobachter didSet
für die Eigenschaft items
implementieren. Sehen Sie sich die Syntax im folgenden Code-Snippet an.
1 |
var items: [String] = [] { |
2 |
didSet { |
3 |
let hasItems = items.count > 0 |
4 |
self.tableView.hidden = !hasItems |
5 |
self.messageLabel.hidden = hasItems |
6 |
}
|
7 |
}
|
Das Konstrukt mag auf den ersten Blick etwas komisch aussehen, also lassen Sie mich erklären, was passiert. Wenn der didSet
-Beobachter aufgerufen wird, prüfen wir nach der Änderung der items
-Eigenschaft, ob die items
-Eigenschaft irgendwelche Elemente enthält. Basierend auf dem Wert der hasItems
-Konstante aktualisieren wir die Benutzeroberfläche. So einfach ist das.
Der didSet
-Beobachter erhält einen konstanten Parameter, der den Wert des alten Werts der Eigenschaft enthält. Es wird im obigen Beispiel weggelassen, weil wir es in unserer Implementierung nicht benötigen. Das folgende Beispiel zeigt, wie es verwendet werden könnte.
1 |
var items: [String] = [] { |
2 |
didSet(oldValue) { |
3 |
if oldValue != items { |
4 |
let hasItems = items.count > 0 |
5 |
self.tableView.hidden = !hasItems |
6 |
self.messageLabel.hidden = hasItems |
7 |
}
|
8 |
}
|
9 |
}
|
Der Parameter oldValue
im Beispiel hat keinen expliziten Typ, da Swift den Typ der Eigenschaft items
kennt. Im Beispiel wird die Benutzeroberfläche nur aktualisiert, wenn der alte Wert vom neuen Wert abweicht.
Ein willSet
-Beobachter funktioniert auf ähnliche Weise. Der Hauptunterschied besteht darin, dass der Parameter, der an den Beobachter willSet
übergeben wird, eine Konstante ist, die den neuen Wert der Eigenschaft enthält. Beachten Sie beim Verwenden von Eigenschaftsobservern, dass sie beim Initialisieren der Instanz nicht aufgerufen werden.
Erstellen und führen Sie die Anwendung aus, um sicherzustellen, dass alles korrekt verbunden ist. Obwohl die Anwendung nicht perfekt ist und ein paar weitere Funktionen nutzen könnte, haben Sie Ihre erste iOS-Anwendung mit Swift erstellt.
Fazit
Im Laufe der letzten drei Lektionen dieser Serie haben Sie eine funktionsfähige iOS-Anwendung mit den objektorientierten Funktionen von Swift erstellt. Wenn Sie Erfahrung in der Programmierung und Entwicklung von Anwendungen haben, müssen Sie bemerkt haben, dass das aktuelle Datenmodell einige Mängel aufweist, um es leicht zu sagen. Das Speichern von Objekten als Zeichenfolgen und das Erstellen eines separaten Arrays zum Speichern des Status eines Objekts ist keine gute Idee, wenn Sie eine geeignete Anwendung erstellen. Ein besserer Ansatz wäre es, eine separate ToDo
-Klasse zum Modellieren von Elementen zu erstellen und diese in der Sandbox der Anwendung zu speichern. Das wird unser Ziel für den nächsten Teil dieser Serie sein.