[Swift] Checklist – episodio II

Finalmente, dopo lunghe peripezie, riesco a scrivere l’articolo riassuntivo sulla terza parte del corso sulle TableViews in Swift di Ray Wenderlich.

Ci eravamo lasciati l’ultima volta con un’applicazione funzionante ma estremamente limitata. Potevamo creare nuove file nella tabella ma non avevamo alcun controllo su di esse.

La terza parte del corso si intitola Aggiungere e rimuovere elementi. Vediamo di che si tratta.

Navigation Controller

Per prima cosa spostiamoci nella nostra Main.storyboard e, selezionato il ViewController, accediamo all’Attributes Inspector. Qui, nella sezione Simulated Metrics, la prima in alto, espandiamo il menu a tendina della Top Bar e selezioniamo l’opzione Translucent Navigation Bar. A cosa serva esattamente non ci è stato spiegato, però suppongo voglia dire che la barra di navigazione in alto sarà traslucida (immagino un effetto puramente estetico, al momento).

Screen Shot 2019-02-06 at 20.14.44
Embedding.

A questo punto, sempre con il ViewController selezionato, andiamo a cliccare, in basso a
destra, sull’icona a forma di scatola con una freccia verso l’interno e selezioniamo Embed In > Navigation controller. Lo stesso può essere ottenuto dalla barra dei menu cliccando su Product > Embed In > Navigation controller. Questo crea una nuova schermata “madre” che controllerà tutte le altre schermate successive (almeno in parte).

Vogliamo adesso che la nostra schermata principale mostri i titoli a caratteri grandi. Questa funzione mi ha sempre confuso molto perché sembra ci siano quattordici modi diversi di applicarla. Quella che usavo di solito non funzionava quindi mi sono limitato a seguire il tutorial.

Rechiamoci quindi in ChecklistViewController, nella funzione ViewDidLoad e scriviamo:

        navigationController?.navigationBar.prefersLargeTitles = true

Torniamo adesso nella storyboard ed aggiungiamo un navigationItem alla nostra TableView. Selezioniamo questo oggetto e, nell’Attributes inspector, alla voce Title, scriviamo Checklists.

Sempre attraverso la Object Library (Cmd-Shift-L) aggiungiamo un bottone alla barra di navigazione (è di tipo Bar Button Item, guarda un po’!). Tramite l’Inspector andiamo a cambiare il system item in Add. In questo modo il bottone avrà l’aspetto di un segno ‘+’.

Tramite l’Assistant Editor (Cmd-Alt-Return) creiamo una IBAction (Interface Builder Action) dal bottone al nostro ChecklistViewController. Dentro questa funzione (che chiameremo addItem, scriveremo:

print("Added item")

Questo ci serve solo per testare che il bottone funzioni. Premerlo infatti farà comparire nella console il messaggio “Added item” anche se, oggettivamente, non sarà successo nulla.

Aggiungere un Checklist Item

Il prossimo passo sarà permettere all’utente di aggiungere un nuovo oggetto alla nostra lista di controllo. Essendo Swift un linguaggio che si basa principalmente (almeno in queste semplici applicazioni) sui messaggi scambiati tra oggetti.

All’interno del file TodoList.swift aggiungiamo un nuovo metodo:

func newTodo() -> ChecklistItem {
        let item = ChecklistItem()
        item.text = randomTitle()
        item.checked = true
        todos.append(item)

        return item
}

Questo metodo non accetta alcun parametro ma ritorna un ChecklistItem.

Al suo interno inizializziamo un ChecklistItem e lo immagazziniamo nella costante item. Nella proprietà .text di quest’ultima immagazziniamo il risultato di una chiamata alla funzione randomTitle. Impostiamo la proprietà .checked su vero in modo che il nostro nuovo oggetto abbia già il segno di completamento. Per finire aggiungiamo questo oggetto appena creato all’array todos e ritorniamo l’oggetto.

Ci spostiamo in ChecklistViewController per modificare il metodo @IBAction func addItem(_ sender: UIBarButtonItem) { … }.

@IBAction func addItem(_ sender: UIBarButtonItem) {
        let newRowIndex = todoList.todos.count
        _ = todoList.newTodo() 
        let indexPath = IndexPath(row: newRowIndex, section: 0)
        tableView.insertRows(at: [indexPath], with: .automatic)
}

Aggiungiamo il risultato della chiamata todoList.newTodo() ad una costante senza nome rappresentata dal trattino basso ( _ ), questo perché non siamo interessati a lavorare con l’oggetto restituito da questo metodo.

Le ultime due righe ci permettono di inserire il risultato di questa chiamata nell’indexPath desiderato (ovvero la coordinata rappresentata dalla sezione nella tabella e dalla fila nella stessa sezione).

Il metodo randomTitle()

Poco fa abbiamo chiamato il metodo randomTitle() e abbiamo immagazzinato il valore ritornato in item.text. Non abbiamo però descritto in cosa consiste questo metodo. Tornando in TodoList.swift:

private func randomTitle() -> String {
        var titles = ["New todo item", "Generic todo", "Fill me out", "I need something to do", "Much todo about nothing"]
        let randomNumber = Int.random(in: 0 ... titles.count - 1)

        return titles[randomNumber]
}

Creiamo un array di String(s), generiamo un numero casuale tra 0 ed il conteggio degli elementi dell’array meno uno (perché contiamo sempre da zero negli array) e ritorniamo l’elemento desiderato corrispondente.

Aggiungere e rimuovere oggetti.

Per permettere all’utente di aggiungere (o rimuovere) oggetti da una tabella dobbiamo chiamare uno dei delegate methods della classe UITableViewController.

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        todoList.todos.remove(at: indexPath.row)
        tableView.deleteRows(at: [indexPath], with: .automatic)
}

Override perché stiamo sovrascrivendo il metodo originario della classe con il nostro. LA prima riga fa sì che quando noi scorriamo con il dito verso sinistra appaia la scritta Delete in bianco su sfondo rosso. La seconda riga fa sì che premendola la nostra riga sparisca ed il suo oggetto venga cancellato dall’array.

Teniamo conto che qui la chiamata todoList.todos.remove(at:) immagazzina in sé il contenuto rimosso, non lo cancella del tutto, però se noi non lo immagazziniamo a sua volta in una costante non potremo più utilizzarlo alla fine della chiamata di questo metodo e sarà, effettivamente, perso per sempre.

Ci muoviamo brevemente nella Main.storyboard per cambiare la priorità dei constraint(s) verticali del label della cella. Modifichiamo quello inferiore a 750 e quello superiore a 751 (la guida indicava 750 ad entrambi senza riportare errori mentre realizzandolo sul mio Xcode mi appariva un errore rosso — quello che non permette di avviare l’applicazione se non viene risolto —).

Aggiungere un nuovo schermo

Tutto quanto fatto finora è stato molto interessante però non dà all’utente la possibilità di scrivere il proprio elemento di controllo. Per fare ciò dobbiamo aggiungere una schermata. Dalla Object Library aggiungiamo quindi una nuova Table View Controller e, trascinando con il tasto Ctrl premuto dal bottone ‘+’ della nostra Checklists Scene, colleghiamola al nostro primo centro di controllo. Ci verrà chiesto quale tipo di segue creare e noi andremo ad indicare show, ovvero quel movimento che spinge il nuovo view controller in cima a quello vecchio.

Creiamo ora un nuovo file premendo Command-N e selezionando Cocoa Touch Class e chiamandolo AddItemViewController. Con il nuovo Table View Controller selezionato nella storyboard accediamo al suo Identity Inspector (Cmd-Alt-3) e nella sezione Custom Class cambiamo il campo Class in AddItemViewController. In questo modo la nostra nuova schermata farà riferimento alla nuova classe (e a tutto il codice in essa contenuto) appena creata.

Non abbiamo però finito con la storyboard: aggiungiamo due bar button items alla barra di navigazione e modifichiamoli nel loro Attributes Inspector affinché mostrino Cancel a sinistra e Add a destra. Aggiungiamo anche un text field nella cella e cambiamo il contenuto della tabella per contenere celle statiche e la sezione affinché contenga una sola fila. Per adesso è più che sufficiente, poiché è sempre meglio creare qualcosa di semplice che funziona per poi aggiungere complessità in seguito.

Apriamo ora l’Assistant Editor (Cmd-Alt-Return) ed operiamo i seguenti collegamenti:

  • ognuno dei due bottoni ed il campo di testo vanno collegati con degli outlets chiamati, rispettivamente, addBarButtoncancelBarButtontextField.
  • ognuno dei due bottoni deve inoltre avere un’azione ad esso collegato.
@IBAction func cancel(_ sender: Any) {
        navigationController?.popViewController(animated: true)
}

@IBAction func done(_ sender: Any) {
        navigationController?.popViewController(animated: true)
        print("Contents of the text field \(textField.text)")
}

Queste due funzioni fanno sì che, per adesso, entrambi i pulsanti ci riportino indietro al view controller principale, chiamando appunto il segue che avevamo connesso poco fa in fase di creazione della nuova schermata.

Reagire alla catena di risposta di iOS

Il funzionamento di iOS è, di base, abbastanza semplice: ogni oggetto invia un messaggio al sistema operativo, questo messaggio si mette in coda e aspetta di essere servito. Questo genera una catena di risposta o, in linguaggio tecnico, una responder chain. Questo fa sì che molte cose buone possano accadere ma è anche ciò che fa rimanere sullo schermo la tastiera quando digitiamo nel campo testuale appena creato. Vediamo come risolverlo poiché servono diversi passaggi.

Per prima cosa rechiamoci in AddItemViewController ed aggiungiamo il seguente metodo:

override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
        return nil
}

Questo ci permette di dire ad iOS che non ci interessa che questa cella venga selezionata e, conseguentemente, evidenziata di grigio.

Nel metodo done(_ sender: Any) di poco sopra possiamo commentare la linea che stampa nella console il messaggio del contenuto del campo di testo poiché non più necessario a questo punto.

Aggiungiamo ora questo metodo:

override func viewWillAppear(_ animated: Bool) {
        textField.becomeFirstResponder()
}

Nel momento in cui la schermata apparirà in seguito alla nostra pressione del tasto ‘+’ (viewWillAppear) il campo di testo (textField) cercherà di diventare il primo risponditore (becomeFirstResponder()), ovvero cercherà di richiedere priorità nella catena di eventi di iOS. Questo ci permetterà di avere la tastiera che compare e, soprattutto, di acquisire controllo sulla possibilità di farla sparire premendo il tasto Invio.

Torniamo adesso nella storyboard e operiamo lì alcune modifiche:

  • Modifichiamo la grandezza del font impiegano nel campo di testo a 17.
  • Eliminiamo il bordo (la prima icona a sinistra delle quattro rappresentanti le tipologie di bordo utilizzabili).
  • Nella sezione Text Input Traits cambiamo la capitalizzazione (capitalization) in Sentences ovvero facciamo in modo che la prima lettera di ogni frase sia maiuscola. Se il nostro oggetto della lista di controllo avrà più frasi al suo interno, qualora mettessimo un punto la lettera successiva verrà inserita automaticamente come maiuscola.
    • Nella stessa sezione cambiamo il tasto di invio in un tasto di completamento: menù a tendina Return Key in Done.
    • Selezioniamo la casella Auto-enable Return Key che ci permetterà di avere il tasto di Invio solo se il campo di testo contiene qualcosa. Questa è una misura di sicurezza per impedirci di inserire elementi vuoti.
  • Nella sezione immediatamente sopra togliamo la spunta da Adjust to Fit in modo che il testo non cambi dimensione cambiando schermata o risoluzione dello schermo. Si tratta di una funzione piuttosto avanzata e non adatta a questo momento.

Eventi di controllo

Nonostante tutti questi cambiamenti la tastiera continua ancora a fluttuare sullo schermo dopo che abbiamo terminato di scrivere. Dobbiamo per prima cosa collegare il campo di testo al metodo done() attraverso un evento di controllo chiamato Did End On ExitPer fare ciò rechiamoci nella storyboard e, clicchiamo con il tasto destro sul campo di testo. Ci apparirà questa immagine:

Screen Shot 2019-02-18 at 20.43.17

In questo caso la funzione è già collegata ma per chi non lo avesse ancora fatto basterà aprire l’Assistant Editor e trascinare dal cerchio vuoto del Did End On Exit fino al metodo Done. Questo collegherà il bottone Add e il campo di testo allo stesso metodo.

Quello che ci serve ora è fare conoscenza con il concetto dei delegati, ovvero di quegli oggetti che portano a compimento un lavoro per conto di qualcun altro. Esistono diversi modi per ottenere questo risultato (ad esempio attraverso diversi protocolli) ma il modo che più sembra andare di moda adesso è quello di creare un estensione alla classe utilizzata e di far sì che essa si conformi al protocollo desiderato.

Creiamo quindi, in AddItemTableViewController, un’estensione:

extension AddItemTableViewController: UITextFieldDelegate {
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()

        return false
    }
}

In questo modo quando premeremo il tasto Return (Invio) sulla nostra tastiera essa sparirà dallo schermo in maniera estremamente graziosa.

Nonostante tutti i nostri sforzi ciò che otteniamo premendo il tasto Add è quello di un todoItem vuoto. Per risolvere questo dobbiamo affidarci ad un metodo delegato del campo di testo che sostituirà il vecchio testo presente nell’oggetto con quello inserito dall’utente. Questo metodo esamina quello che l’utente inserisce e lo immagazzina nel nostro oggetto, prossimo per essere utilizzato.

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        guard let oldText = textField.text,
            let stringRange = Range(range, in: oldText) else {
                return false
        }

        let newText = oldText.replacingCharacters(in: stringRange, with: string)

        if newText.isEmpty {
            addBarButton.isEnabled = false
        } else {
            addBarButton.isEnabled = true
        }

        return true
    }
  1. Per prima cosa controlliamo che il campo di testo contenga qualcosa (anche un testo vuoto!) attraverso l’espressione guard let e creiamo un ambito (range) all’interno del quale inserire il nostro nuovo testo.
  2. Creiamo una costante che rimpiazzerà il vecchio testo con quello nuovo.
  3. Se il nuovo testo è vuoto non abiliteremo il pulsante di aggiunta, mentre lo abiliteremo in caso contrario.

Nella prossima lezione vedremo come fare sì che tutto quanto abbiamo creato venga mostrato a schermo per davvero!

A presto!

Published by Michele Galvagno

Professional Musical Scores Designer and Engraver Graduated Classical Musician (cello) and Teacher Tech Enthusiast and Apprentice iOS / macOS Developer Grafico di Partiture Musicali Professionista Musicista classico diplomato (violoncello) ed insegnante Appassionato di tecnologia ed apprendista Sviluppatore iOS / macOS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Create your website with WordPress.com
Get started
%d bloggers like this: