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.
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.
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.
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.
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.
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.
ViewController
classe sono accessibili solo da ViewController
classe.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")
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.
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à.
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.
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 cambiatodidSet
: richiamato dopo che il valore è cambiatoPer 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.
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!