Swift From Scratch controllo accessi e osservatori di proprietà

Nella lezione precedente, abbiamo aggiunto la possibilità di creare elementi da fare. Mentre questa aggiunta ha reso l'applicazione un po 'più utile, sarebbe anche conveniente aggiungere la possibilità di contrassegnare gli elementi come fatti ed eliminare elementi. Questo è ciò su cui ci concentreremo in questa lezione.

Prerequisiti

Se desideri seguirmi, assicurati di aver installato Xcode 8.3.2 o successivo sulla tua macchina. Puoi scaricare Xcode 8.3.2 dall'App Store di Apple.

1. Eliminazione di articoli

Per eliminare elementi, dobbiamo implementare due metodi aggiuntivi di UITableViewDataSource protocollo. Per prima cosa è necessario indicare alla tabella le righe che possono essere modificate implementando il comando tableView (_: canEditRowAt :) metodo. Come puoi vedere nello snippet di codice seguente, l'implementazione è semplice. Diciamo alla tabella che ogni riga è modificabile restituendo vero.

func tableView (_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool return true

Il secondo metodo che ci interessa è tableView (_: commit: forRowAt :). L'implementazione è un po 'più complessa ma abbastanza facile da comprendere.

func tableView (_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) if editingStyle == .delete // Aggiorna elementi items.remove (a: indexPath.row) // Aggiorna tabella Visualizza tableView.deleteRows (a : [indexPath], con: .right)

Iniziamo controllando il valore di editingStyle, un'enumerazione di tipo UITableViewCellEditingStyle. Eliminiamo un oggetto solo se il valore di editingStyle è uguale a UITableViewCellEditingStyle.delete.

Swift è più intelligente di quello, però. Perché lo sa editingStyle è di tipo UITableViewCellEditingStyle, possiamo omettere UITableViewCellEditingStyle, il nome dell'enumerazione e scrivi .Elimina, il valore membro dell'enumerazione a cui siamo interessati. Se sei nuovo alle enumerazioni in Swift, ti consiglio di leggere questo suggerimento rapido sulle enumerazioni in Swift.

Successivamente, aggiorniamo l'origine dati della vista tabella, elementi, invocando rimuovere (A :) sul elementi proprietà, passando l'indice corretto. Aggiorniamo anche la vista tabella invocando DeleteRows (presso: con :) sopra tableView, passando in un array con indexPath e .destra per specificare il tipo di animazione. Come abbiamo visto in precedenza, possiamo omettere il nome dell'enumerazione, UITableViewRowAnimation, poiché Swift conosce il tipo del secondo argomento UITableViewRowAnimation.

Ora l'utente dovrebbe essere in grado di eliminare elementi dall'elenco. Costruisci ed esegui l'applicazione per testarlo.

2. Verifica degli articoli

Per contrassegnare un articolo come fatto, aggiungeremo un segno di spunta alla riga corrispondente. Ciò implica che dobbiamo tenere traccia degli articoli che l'utente ha contrassegnato come fatto. A tal fine, dichiareremo una nuova proprietà che gestisce questo per noi. Dichiarare una proprietà variabile, CheckedItems, di tipo [Stringa], e inizializzarlo con una matrice vuota.

var checkedItems: [String] = []

Nel tableView (_: cellForRowAt :), controlliamo se CheckedItems contiene l'oggetto rispettivo invocando il contiene (_ :) metodo, passando l'articolo che corrisponde alla riga corrente. Il metodo restituisce vero Se CheckedItems contiene articolo.

func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell // Fetch Item let item = items [indexPath.row] // Dequeue Cell let cella = tableView.dequeueReusableCell (withIdentifier: "TableViewCell", per: indexPath // Configure Cell cell.textLabel? .Text = item if checkedItems.contains (item) cell.accessoryType = .checkmark else cell.accessoryType = .none restituisci cella

Se articolo è trovato in CheckedItems, abbiamo impostato la cella accessoryType proprietà a .segno di spunta, un valore membro del UITableViewCellAccessoryType enumerazione. Se articolo non è stato trovato, ci ricadiamo .nessuna come il tipo di accessorio della cella.

Il prossimo passo è aggiungere la possibilità di contrassegnare un oggetto come fatto implementando un metodo di UITableViewDelegate protocollo, tableView (_: didSelectRowAt :). In questo metodo delegato, prima chiamiamo deselectRow (a: animato :) sopra tableView per deselezionare la riga che l'utente ha toccato.

// MARK: - Table View Delegate Methods func tableView (_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) tableView.deselectRow (a: indexPath, animato: true) // Fetch Item let item = items [indexPath.row] // Recupera cella lascia cella = tableView.cellForRow (a: indexPath) // Trova indice di elemento let index = checkedItems.index (di: item) se let index = index checkedItems.remove (at: index) cell? .AccessoryType =. none else checkedItems.append (item) cell? .accessoryType = .checkmark

Quindi recuperiamo l'oggetto corrispondente da elementi e un riferimento alla cella che corrisponde alla fila toccata. Noi chiediamo CheckedItems per l'indice dell'elemento corrispondente invocando indice di:). Questo metodo restituisce un optional Int. Se CheckedItems contiene articolo, lo rimuoviamo da CheckedItems e impostare il tipo di accessorio della cella su .nessuna. Se CheckedItems non contiene articolo, lo aggiungiamo a CheckedItems e impostare il tipo di accessorio della cella su .segno di spunta.

Con queste aggiunte, l'utente è ora in grado di contrassegnare gli articoli come completati. Crea ed esegui l'applicazione per assicurarti che tutto funzioni come previsto.

3. Salvare lo stato

L'applicazione attualmente non salva lo stato tra i lanci. Per risolvere questo, stiamo andando a memorizzare il elementi e CheckedItems array nel database delle impostazioni predefinite dell'utente dell'applicazione.

Passaggio 1: caricamento dello stato

Inizia creando due metodi di supporto, loadItems () e loadCheckedItems (). Notare la privato parola chiave che prefissa ogni metodo di supporto. Il privato keyword dice a Swift che questi metodi sono accessibili solo dall'interno ViewController classe.

// MARK: metodi di helper privati ​​private func loadItems () let userDefaults = UserDefaults.standard se let items = userDefaults.object (forKey: "items") as? [String] self.items = items private func loadCheckedItems () let userDefaults = UserDefaults.standard se let checkedItems = userDefaults.object (forKey: "checkedItems") as? [String] self.checkedItems = checkedItems

Il privato la parola chiave fa parte di Swift controllo di accesso. Come suggerisce il nome, il controllo di accesso definisce quale codice ha accesso a quale codice. I livelli di accesso si applicano a metodi, funzioni, tipi, ecc. Apple si riferisce semplicemente a entità. Esistono cinque livelli di accesso: aperto, pubblico, interno, file-privato e privato.

  • Apri / PPUBBLICA: Le entità contrassegnate come aperte o pubbliche sono accessibili da entità definite nello stesso modulo e anche da altri moduli. Questo è l'ideale per esporre l'interfaccia di un framework. Ci sono molte differenze tra i livelli di accesso aperto e pubblico. Puoi leggere ulteriori informazioni su queste differenze in The Swift Programming Language.
  • Interno: Questo è il livello di accesso predefinito. In altre parole, se non viene specificato alcun livello di accesso, si applica questo livello di accesso. Un'entità con un livello di accesso interno è accessibile solo da entità definite nello stesso modulo.
  • File-privato: Un'entità dichiarata come file-privato è accessibile solo da entità definite nello stesso file sorgente. Ad esempio, i metodi di helper privati ​​definiti in ViewController classe sono accessibili solo da ViewController classe.
  • Privato: Privato è molto simile al file-privato. L'unica differenza è che un'entità dichiarata come privata è accessibile solo all'interno della dichiarazione in cui è inclusa. Ad esempio, se creiamo un'estensione per il ViewController classe in ViewController.swift, qualsiasi entità contrassegnata come file-private non sarebbe accessibile nell'estensione, ma le entità private sarebbero accessibili.

L'implementazione dei metodi di supporto è semplice se si ha familiarità con UserDefaults classe. Per facilità d'uso, memorizziamo un riferimento all'oggetto standard di default dell'utente in una costante denominata userDefaults. In caso di loadItems (), noi chiediamo userDefaults per l'oggetto associato alla chiave "elementi" e downcast su una serie opzionale di stringhe. Scartiamo l'opzione in modo sicuro, il che significa che memorizziamo il valore nella costante elementi se l'opzionale non lo è zero, e assegnare il valore al elementi proprietà del controller della vista.

Se la Se la dichiarazione sembra confusionaria, quindi date un'occhiata ad una versione più semplice di loadItems () metodo nel seguente esempio. Il risultato è identico; l'unica differenza è concisione.

private func loadItems () let userDefaults = UserDefaults.standard let savedItems = userDefaults.object (forKey: "items") as? [String] se let items = storedItems self.items = items

L'implementazione di loadCheckedItems () è identico ad eccezione della chiave utilizzata per caricare l'oggetto memorizzato nel database dei valori predefiniti dell'utente. Mettiamo loadItems () e loadCheckedItems () da utilizzare aggiornando il viewDidLoad () metodo.

override func viewDidLoad () super.viewDidLoad () // Imposta titolo title = "To Do" // Popola elementi items = ["Acquista latte", "Termina tutorial", "Riproduci Minecraft"] // Carica stato loadItems () loadCheckedItems () // Register Class for Cell Reuse tableView.register (UITableViewCell.self, forCellReuseIdentifier: "TableViewCell")

Passaggio 2: Salvataggio dello stato

Per salvare lo stato, implementiamo altri due metodi di supporto privati, saveItems () e saveCheckedItems (). La logica è simile a quella di loadItems () e loadCheckedItems (). La differenza è che archiviamo i dati nel database dei valori predefiniti dell'utente. Assicurati che i tasti usati nel setObject (_: Forkey :) le chiamate corrispondono a quelle usate in loadItems () e loadCheckedItems ().

private func saveItems () let userDefaults = UserDefaults.standard // Aggiorna User Default default userDefaults.set (items, forKey: "items") userDefaults.synchronize () func privato saveCheckedItems () let userDefaults = UserDefaults.standard // Update User Defaults userDefaults.set (checkedItems, forKey: "checkedItems") userDefaults.synchronize ()

Il sincronizzare() la chiamata non è strettamente necessaria. Il sistema operativo si assicurerà che i dati memorizzati nel database di default dell'utente siano scritti su disco ad un certo punto. Invocando sincronizzare(), tuttavia, si comunica esplicitamente al sistema operativo di scrivere eventuali modifiche in sospeso sul disco. Questo è utile durante lo sviluppo, perché il sistema operativo non scriverà le modifiche sul disco se si uccide l'applicazione. Potrebbe quindi sembrare che qualcosa non funzioni correttamente.

Dobbiamo invocare saveItems () e saveCheckedItems () in un numero di posti. Per iniziare, chiama saveItems () quando un nuovo elemento viene aggiunto alla lista. Lo facciamo nel metodo delegato del AddItemViewControllerDelegate protocollo.

// MARK: Aggiungi Item View Controller Delegate Methods func controller (_ controller: AddItemViewController, didAddItem: String) // Aggiorna origine dati items.append (didAddItem) // Salva stato saveItems () // Ricarica tabella Visualizza tableView.reloadData ( ) // Ignora Aggiungi elemento Visualizza Controller licenziamento (animato: vero)

Quando lo stato di un oggetto cambia nel tableView (_: didSelectRowAt :), aggiorniamo CheckedItems. È una buona idea invocare anche saveCheckedItems () a quel punto.

// MARK: - Table View Delegate Methods func tableView (_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) tableView.deselectRow (a: indexPath, animato: true) // Fetch Item let item = items [indexPath.row] // Recupera cella lascia cella = tableView.cellForRow (a: indexPath) // Trova indice di elemento let index = checkedItems.index (di: item) se let index = index checkedItems.remove (at: index) cell? .AccessoryType =. none else checkedItems.append (item) cell? .accessoryType = .checkmark // Salva stato saveCheckedItems ()

Quando un elemento viene eliminato, entrambi elementi e CheckedItems sono aggiornati. Per salvare questo cambiamento, chiamiamo entrambi saveItems () e saveCheckedItems ().

func tableView (_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) if editingStyle == .delete // Fetch Item let item = items [indexPath.row] // Aggiorna articoli items.remove (a: indexPath .row) if let index = checkedItems.index (of: item) checkedItems.remove (at: index) // Aggiorna tabella Visualizza tableView.deleteRows (a: [indexPath], con: .right) // Salva stato saveItems () saveCheckedItems ()

Questo è tutto. Crea ed esegui l'applicazione per testare il tuo lavoro. Gioca con l'applicazione e forza chiudilo. Quando si avvia nuovamente l'applicazione, l'ultimo stato conosciuto deve essere caricato e visibile.

4. Osservatori di proprietà

L'esperienza utente dell'applicazione è un po 'carente al momento. Quando ogni elemento viene eliminato o quando l'applicazione viene avviata per la prima volta, l'utente vede una vista tabella vuota. Questo non è eccezionale. Possiamo risolvere questo mostrando un messaggio quando non ci sono articoli. Questo mi darà anche l'opportunità di mostrarti un'altra funzionalità di Swift, osservatori di proprietà.

Passaggio 1: aggiunta di un'etichetta

Iniziamo aggiungendo un'etichetta all'interfaccia utente per mostrare il messaggio. Dichiara un outlet chiamato messageLabel di tipo UILabel nel ViewController classe, aperto Main.storyboard, e aggiungere un'etichetta alla vista del controller della vista.

@IBOutlet var messageLabel: UILabel!

Aggiungi i vincoli di layout necessari all'etichetta e collegalo al controller della vista messageLabel presa nel Connections Inspector. Imposta il testo dell'etichetta su Non hai nessuna cosa da fare. e centrare il testo dell'etichetta nel file Ispettore degli attributi.

Passaggio 2: implementazione di un osservatore di proprietà

L'etichetta del messaggio dovrebbe essere visibile solo se elementi non contiene elementi Quando ciò accade, dovremmo anche nascondere la vista tabella. Potremmo risolvere questo problema aggiungendo vari controlli nel ViewController classe, ma un approccio più conveniente ed elegante è quello di utilizzare un osservatore di proprietà.

Come suggerisce il nome, gli osservatori di proprietà osservano una proprietà. Un osservatore di proprietà viene invocato ogni volta che una proprietà cambia, anche quando il nuovo valore è uguale al vecchio valore. Esistono due tipi di osservatori di proprietà.

  • sarà impostato: invocato prima che il valore sia cambiato
  • didSet: richiamato dopo che il valore è cambiato

Per il nostro scopo, implementeremo il didSet osservatore per il elementi proprietà. Dai un'occhiata alla sintassi nello snippet di codice seguente.

var items: [String] = [] didSet let hasItems = items.count> 0 tableView.isHidden =! hasItems messageLabel.isHidden = hasItem

Il costrutto può sembrare un po 'strano all'inizio, quindi lascia che ti spieghi cosa sta succedendo. Quando il didSet l'osservatore di proprietà è invocato, dopo il elementi la proprietà è cambiata, controlliamo se il elementi la proprietà contiene alcuni elementi. Basato sul valore del hasItems costante, aggiorniamo l'interfaccia utente. E 'così semplice.

Il didSet all'osservatore viene passato un parametro costante che contiene il valore del vecchio valore della proprietà. È omesso nell'esempio sopra, perché non ne abbiamo bisogno nella nostra implementazione. L'esempio seguente mostra come potrebbe essere utilizzato.

var items: [String] = [] didSet (oldValue) if oldValue! = items let hasItems = items.count> 0 tableView.isHidden =! hasItems messageLabel.isHidden = hasItem

Il oldValue parametro nell'esempio non ha un tipo esplicito, perché Swift conosce il tipo di elementi proprietà. Nell'esempio, aggiorniamo solo l'interfaccia utente se il vecchio valore differisce dal nuovo valore.

UN sarà impostato l'osservatore funziona in modo simile. La differenza principale è che il parametro è passato a sarà impostato l'osservatore è una costante che tiene il nuovo valore della proprietà. Quando si usano osservatori di proprietà, tenere presente che non vengono richiamati quando l'istanza viene inizializzata.

Costruisci ed esegui l'applicazione per assicurarti che tutto sia collegato correttamente. Anche se l'applicazione non è perfetta e potrebbe utilizzare alcune funzionalità aggiuntive, hai creato la tua prima applicazione iOS utilizzando Swift.

Conclusione

Nel corso delle ultime tre lezioni di questa serie, hai creato un'applicazione iOS funzionale utilizzando le funzionalità orientate agli oggetti di Swift. Se hai esperienza con la programmazione e lo sviluppo di applicazioni, devi aver notato che l'attuale modello di dati presenta alcune lacune, per dirla alla leggera.

Memorizzare elementi come stringhe e creare una matrice separata per memorizzare lo stato di un oggetto non è una buona idea se stai costruendo un'applicazione corretta. Un approccio migliore sarebbe quello di creare un separato Fare classe per modellare gli oggetti e memorizzarli nella sandbox dell'applicazione. Questo sarà il nostro obiettivo per la prossima lezione di questa serie.

Nel frattempo, dai uno sguardo ad alcuni dei nostri altri corsi e tutorial sullo sviluppo di iOS in lingua Swift!