Advertisement
  1. Code
  2. Swift

Swift from Scratch: Zugriffskontrolle und Objektbeobachter

Scroll to top
Read Time: 13 min
This post is part of a series called Swift From Scratch.
Swift From Scratch: Delegation and Properties
Swift From Scratch: Initialization and Initializer Delegation

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 findfunktion, 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 die ViewController-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.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.