Lekce 5 - Seznámení se s důležitou komponentou TableView

Swift iOS Seznámení se s důležitou komponentou TableView

Unicorn College ONEbit hosting Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Jednoduchá kalkulačka pro iOS ve Swift, jsme si naprogramovali jednoduchou iOS kalkulačku ve Swift. K naskládání komponent na formulář jsme použili StackView, které je skládalo pod sebe nebo jsme si u něj mohli nastavit i vodorovný směr. V dnešním Swift tutoriálu se budeme věnovat TableView. Je totiž základem spousty aplikací a pravděpodobně se mu nevyhnete. Ať už si otevřete aplikaci pro zprávy, volání, poznámky a hromadu dalších, díváte se v první řadě na TableView.

TableView je ideální způsob, jak uživateli prezentovat spoustu dat nebo chcete-li jejich kolekci. Může to být seznam úkolů, kontaktů, hudebních alb apod. Právě v takových případech je TableView jasná volba. Umožní vám snadno zobrazit prakticky neomezeně prvků či objektů pěkně pod sebou a vyřešit scrollování či jejich výběr. Velmi snadno také provedete mazání.

Vytvoření projektu

Teorie by mohla pro úvod stačit a pojďme si rovnou ukázat jednoduchý způsob, jak s TableView začít. Připravte si buď zbrusu nový Xcode projekt s iOS aplikací (Single View App) nebo použijte ten z minulých lekcí.

Nyní si najděte TableView (ne Table View Controller, k němu se ještě dostaneme) v knihovně objektů a přetáhněte ho na váš controller umístěný v Main.storyboard. Ideálně nastavte constraints, které mohou být v tomto případě 0 od všech čtyř stran.

Table view v Xcode pro iOS ve Swift

TableView tedy máme, jak v něm zobrazit data? Na to bude potřeba zavítat do kódu a to konkrétně do souboru ViewController.swift. Nejdříve musíme určit, že tento controller slouží jako zdroj dat a delegát pro TableView.

V našem controlleru implementujeme následující dva protokoly:

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

To ovšem nestačí. Nyní musíme přidanému TableView nastavit, že náš controller je jeho DataSource a také Delegate. To abychom mohli vybrat zdroj dat a také reagovat na události, jako je výběr řádku (respektive buňky Cell v řeči iOS). Můžeme si dokonce vybrat, jak toto provést.

Propojení TableView s controllerem pomocí myši

První možností je DataSource a Delegate nastavit přímo v UI designeru, kdy váš označený TableView přetáhnete za držení Ctrl nebo pravým klikem na ViewController a vyberete dataSource. To samé platí pro delegate. Nebo kliknete pravým na TableView v seznamu komponent a přetáhnete dataSource a delegate odtud.

Nastavení dataSource a delegate TableView

Tímto jsme vlastně TableView řekli, že náš controller bude reagovat na události TableView a zároveň poskytovat data. Díky tomu nás tak TableView komponenta může upozornit, že uživatel zvolil nějakou položku či provedl další akci.

Propojení TableView s controllerem pomocí kódu

Druhou možností je DataSource a Delegate nastavit v kódu v metodě viewDidLoad(). Osobně preferuji tento způsob, propojení mám na očích a vždy tak vím, že jsem na to nezapomněl. Kdykoliv vám u TableView nebude něco fungovat, zkontrolujte nejdříve, že jej máte správně propojené.

Nejdříve musíte vašemu TableView vytvořit Outlet, což jsme probrali již v minulých lekcích. Poté pouze nastavíte vaši třídu (tedy controller) jako dataSource a delegate.

override func viewDidLoad() {
        super.viewDidLoad()

        tableView.dataSource = self
        tableView.delegate = self
}

Nezbytné přípravy máme za sebou a nyní konečně v TableView něco zobrazíme.

Implementace protokolů

Aktuálně vám nebude fungovat Build, protože jsme určili, že naše třída bude implementovat dané protokoly, ale žádný kód jsme do ní zatím nepřidali. Nyní se očekává, že naše třída zvládne TableView poskytnout data. Pro začátek stačí přidat dvě metody:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

}

První metoda vrací kolik budeme mít řádků. Zatím si práci nebudeme komplikovat více sekcemi. Druhá má poté na starost vrátit danou buňku podle indexu řádky (respektive buňky).

Protože je začátek funkcí stejný, v případě první začněte psát numberOfRowsIn.. a Xcode vám samo doplní správnou metodu. Právě k tomuto slouží dvojí názvy parametrů. Ten první slouží pro identifikaci metody "zvenčí" a druhý používáte v těle metody. Stejně tak si nechte doplnit druhou metodu tak, že začnete psát cellForRow...

Příprava dat

Vytvoříme si jednoduché pole, které bude reprezentovat položky to-do listu. Bývá zvykem proměnné a konstanty deklarovat hned pod názvem třídy.

var todos = ["Buy coffee", "Take out the trash", "Netflix and chill"]

Je úplně jedno, co sem napíšete. Cílem je mít jakékoliv pole s nějakými hodnotami, které si v našem TableView zobrazíme. Nyní upravíme naše dvě metody.

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return todos.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel?.text = todos[indexPath.row]
        return cell
}

Nic složitého. První metoda vrací počet prvků a druhá vytváří buňku a nastavuje jí text podle jejího indexu. Texty se tahají z našeho pole. Aplikaci můžete spustit a uvidíte všechny prvky vašeho pole pod sebou.

TODO list v Xcode a Swift

TableView pod pokličkou

Zároveň si zapamatujte, že takto by metoda s cellForRowAt neměla nikdy vypadat. Chtěl jsem pouze co nejrychleji ukázat funkční TableView. Nyní si povíme jak TableView funguje "pod pokličkou".

Scrollování položkami bude velmi plynulé i v případě stovky buněk. TableView je totiž chytrá komponenta a drží pouze buňky, které je nutné zobrazit na displeji. Jednoduše můžeme říci, že buňky recykluje, k čemuž je potřeba důležitá metoda:

let recycledCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

Tyto metody jsou ve skutečnosti dvě, druhá nemá druhý parametr a nikdy byste ji neměli používat. Jedná se o starou verzi, která zůstala zachována kvůli zpětné kompatibilitě.

Možná jste zbystřili parametr withIdentifier, který musíme nastavit, aby vše fungovalo. Před použitím ale musíme ještě připravit TableView. Otevřete designer a po vybrání komponenty nastavte v Attributes inspector hodnotu pro Prototype Cells na 1.

Nastavení Prototype Cells TableView v Xcode

Následně je třeba vybrat tuto prototyp buňku, která se objeví v náhledu. Kliknout můžete přímo v náhledu nebo ji vybrat v seznamu komponent tohoto controlleru nalevo.

Výběr prototyp buňky v TableView

Poté stačí opět v Attributes inspector nastavit Identifier na "cell", který máme již v kódu výše. Můžete si samozřejmě zvolit cokoliv jiného.

TableView Attribute inspector v Xcode

Nyní stačí aplikaci spustit a měli byste opět vidět své to-do.

A proč celá ta práce? Tímto jsme správně použili TableView a naše komponenta je připravená klidně na zobrazování stovek řádků. Ve skutečnosti totiž vždy existují pouze ty, které jsou vidět na displeji, takže velký počet dat nezpomalí aplikaci. V tomto případě nás to určitě trápit nemusí, ale ukázali jsme si korektní řešení.

Je tu také druhý benefit. Jsme na dobré cestě k vytvoření komplexnější buňky, respektive řádku TableView. Právě to nám naše Prototype Cell umožňuje. Jednoduše bychom na ní přetáhli požadované komponenty a vytvořili speciální třídu. To si ukážeme v pozdějších lekcích kurzu.

Výběr a mazání položek

Nyní si ukážeme, jak v TableView vybírat a také mazat jednotlivé položky.

Vybrání položky

Pro situaci, kdy uživatel vybere položku, tu máme připravenou metodu:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {   }

Opět můžete začít psát pouze didSelect... a Xcode vám metodu nabídne a samo vytvoří. Zatím nebudeme po vybrání řádku dělat nic extra, pouze si vypíšeme (do Output okna v Xcode) zvolený text, v našem případě tedy zvolené to-do.

Metodu doplníme o volání funkce print(). Z parametru indexPath si přes vlastnost row zjistíme, jaký řádek byl vlastně vybrán, a ten použijeme jako index pro naše todos[] pole.

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print(todos[indexPath.row])
}

Po výběru řádku se vám nyní vypíše vybrané to-do do konzole. Ještě vyřešíme mazání a tím zatím seznámení s TableView ukončíme.

Odstranění položky

Na mazání položek tu opět máme připravenou metodu:

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {  }

Přidáním této metody vlastně dáváme najevo, že chceme TableView modifikovat. Nyní jsme již ve stavu, kdy se vám při swipe gestu doleva zobrazí tlačítko Delete u jednotlivých řádků. Zatím ale nebude fungovat.

Pomocí parametru editingStyle si v těle metody zjistíme, jestli jde o mazání a pokud ano, tak tento prvek smažeme. Nejdříve z našeho zdrojového pole todos[] a následně ještě z TableView, kde si můžeme vybrat animaci. Kód poté vypadá následovně.

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
                todos.remove(at: indexPath.row)
                tableView.deleteRows(at: [indexPath], with: .fade)
        }
}

Animaci si můžete vybrat podle sebe, často se pro zjednodušení a konzistenci používá volba .automatic.

TableView staticky

Na závěr úvodního seznámení si ukážeme, jak vytvořit TableView, když přesně víme, co budeme mít za buňky, případně rovnou sekce. Jedná se tedy např. o nějaké menu a jinou situaci, než když položky čteme z nějaké kolekce. Tento způsob se skvěle hodí, pokud chceme nabídnout v aplikaci komplexnější možnosti nastavení, abychom se přiblížili systému iOS, kde je v nastavení zařízení TableView na každém kroku.

Přetáhněte si do projektu Table View Controller, který je třeba pro statický TableView. Poté stačí TableView označit a v Attributes inspektoru nastavit Content na Static Cells. Následně si můžete vybrat počet sekcí. Každá může mít vlastní nadpis a také patičku. Jednotlivé sekce poté stačí označit a vše potřebné nastavit, včetně jednotlivých řádků.

Nastavení sekcí statického TableView v Xcode

Jednotlivé buňky nemusí být pouze řádek textu. Pro každou můžete zvlášť nastavit Style a mít např. text + podtitulek, přidat obrázek a tak podobně. Často se nastavuje také vlastnost Accessory (opět v Attributes inspektor), která v levé části buňky zobrazí např. šipku a tím uživateli naznačí, že volba vede na další obrazovku a tak podobně.

Sekce v statickém TableView v Xcode

Pro náš nový controller potřebujeme ještě třídu. Přidáme soubor, ale v dialogu místo Swift file vybereme Cocoa Touch Class. V novém dialogu nastavíme, že jde o subclass UITableViewController a pojmenujeme ho např. SettingsTableViewController. Teď již jen stačí v Main.storyboard vybrat náš nový TableViewController a v Identity inspector mu nastavit Class na nově vytvořenou třídu SettingsTableViewController.

Statický TableView zakončíme reakcí na výběr buňky. V SettingsTableViewController nemusíme řešit DataSource ani Delegate, protože to vše řeší třída UITableViewController. Stačí nám tak pouze implementovat didSelectRowAt metodu. Pouze před ní potřebujeme override, abychom mohli poskytnout vlastní implementaci.

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("Selected row \(indexPath.row) in section \(indexPath.section)")
}

Opět bude stačit výpis pro kontrolu. Ve skutečné implementaci by se celkem hodil switch (jedno z mála míst, kde má smysl), zvlášť, pokud byste měli mnoho sekcí a řádků. V této metodě by například mohl switch řešit pouze sekce a jednotlivé sekce by měly vlastní metody ve stylu handleFirstSection(row: Int) a tak dále. Mohl by vypadat např. takto:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("Selected row \(indexPath.row) in section \(indexPath.section)")

        switch indexPath.section {
                case 0:
                        handleFirstSection(rowIndex: indexPath.row)
                case 1:
                        handleSecondSection(rowIndex: indexPath.row)
                default:
                        break
        }
}

func handleFirstSection(rowIndex: Int) {
        switch rowIndex {
                case 0:
                        // Show account detail
                        break
                case 1:
                        // Navigate to settings
                        break
                default:
                        break
        }
}

func handleSecondSection(rowIndex: Int) {

}

Tímto naše úvodní seznámení s TableView komponentou končí. V následujících lekcích tyto znalosti využijeme pro vytvoření skutečné TODO aplikace včetně databáze. Příště, v lekci Neobjevujte kolo, použijte CocoaPods, se naučíme používat CocoaPods, který nám umožní využívat balíčkovací systém a snadnou instalaci různých knihoven.


 

Stáhnout

Staženo 8x (82.88 kB)
Aplikace je včetně zdrojových kódů v jazyce Swift

 

 

Článek pro vás napsal Filip Němeček
Avatar
Jak se ti líbí článek?
1 hlasů
Autor se příležitostně věnuje vývoji iOS aplikací či těch webových za pomocí frameworku Django. Aktuální projekt: hrejzdarma.cz Twitter: @nemecek_f (neprogramátorské tweety)
Miniatura
Všechny články v sekci
Vyvíjíme iOS aplikace ve Swift
Miniatura
Následující článek
Neobjevujte kolo, použijte CocoaPods
Aktivity (4)

 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!