Swift From Scratch Tillgångskontroll och fastighetsobservatörer

I den tidigare lektionen har vi lagt till möjligheten att skapa uppgiftsobjekt. Medan denna tillägg har gjort programmet lite mer användbar, skulle det också vara bekvämt att lägga till möjligheten att markera objekt som färdiga och radera objekt. Det är vad vi ska fokusera på i den här lektionen.

förutsättningar

Om du vill följa med mig, se till att du har Xcode 8.3.2 eller högre installerad på din maskin. Du kan ladda ner Xcode 8.3.2 från Apples App Store.

1. Radera objekt

För att radera objekt måste vi genomföra två ytterligare metoder för UITableViewDataSource protokoll. Vi måste först se tabellvyn vilka rader som kan redigeras genom att implementera Tableview (_: canEditRowAt :) metod. Som du kan se i koden nedan är implementeringen enkel. Vi berättar på tabellen att varje rad kan redigeras genom att återvända Sann.

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

Den andra metoden vi är intresserade av är Tableview (_: commit: forRowAt :). Implementeringen är lite mer komplex men lätt att förstå.

func tableView (_ tableView: UITableView, commit redigeringStyle: UITableViewCellEditingStyle, förRowAt indexPath: IndexPath) om redigeringStyle == .delete // Uppdatera objekt items.remove (på: indexPath.row) // Uppdatera tabellvy tableView.deleteRows : [indexPath], med: .right)

Vi börjar med att kontrollera värdet av editingStyle, en uppräkning av typ UITableViewCellEditingStyle. Vi tar bara bort ett objekt om värdet av editingStyle är lika med UITableViewCellEditingStyle.delete.

Swift är smartare än det. Eftersom det vet det editingStyle är av typ UITableViewCellEditingStyle, vi kan utelämna UITableViewCellEditingStyle, namnet på uppräkningen och skriv .radera, medlemsvärdet av den uppräkning som vi är intresserade av. Om du är ny på uppräkningar i Swift rekommenderar jag dig att läsa detta snabba tips om uppräkningar i Swift.

Därefter uppdaterar vi tabellvyns datakälla, objekt, genom att åberopa ta bort (vid :) på objekt egendom, passerar i rätt index. Vi uppdaterar även tabellvyn genom att åberopa deleteRows (vid: med :) på Tableview, passerar i en array med indexPath och .höger för att ange animationstypen. Som vi såg tidigare kan vi utesluta namnet på uppräkningen, UITableViewRowAnimation, eftersom Swift vet vilken typ av det andra argumentet är UITableViewRowAnimation.

Användaren ska nu kunna radera objekt från listan. Bygg och kör programmet för att testa detta.

2. Kontrollera av objekt

För att markera ett föremål som gjort, lägger vi till en ruta i motsvarande rad. Det innebär att vi måste hålla reda på de saker som användaren har markerat som gjort. Därför förklarar vi en ny egendom som hanterar detta för oss. Förklara en variabel egenskap, checkedItems, av typ [Sträng], och initiera den med en tom matris.

var checkedItems: [String] = []

I Tableview (_: cellForRowAt :), vi kontrollerar om checkedItems innehåller respektive objekt genom att åberopa innehåller (_ :) metod, passerar i objektet som motsvarar den aktuella raden. Metoden returnerar Sann om checkedItems innehåller Artikel.

func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell // Hämta Item let item = objekt [indexPath.row] // Dequeue Cell låt cell = tableView.dequeueReusableCell (withIdentifier: "TableViewCell", för: indexPath ) // Konfigurera Cell cell.textLabel? .Text = objekt om checkedItems.contains (item) cell.accessoryType = .checkmark else cell.accessoryType = .none returnera cell

Om Artikel finns i checkedItems, vi satte cellens accessoryType egendom till .bock, ett medlemsvärde av UITableViewCellAccessoryType uppräkning. Om Artikel finns inte, vi faller tillbaka till .ingen som cellens tillbehörstyp.

Nästa steg är att lägga till möjligheten att markera ett objekt som gjort genom att implementera en metod av UITableViewDelegate protokoll, Tableview (_: didSelectRowAt :). I denna delegerade metod kallar vi först deselectRow (vid: animerade :) på Tableview för att avmarkera raden som användaren knackade på.

// MARK: - Tabellvisning Delegerade metoder func tableView (_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) tableView.deselectRow (vid: indexPath, animated: true) // Hämta objektlåtobjekt = objekt [indexPath.row] // Hämta cellen låt cellen = tableView.cellForRow (vid: indexPath) // Hitta index över objekt let index = checkedItems.index (av: objekt) om låt index = index checkedItems.remove (vid: index) cell? .AccessoryType =. ingen annars checkedItems.append (item) cell? .accessoryType = .checkmark

Vi hämtar sedan motsvarande föremål från objekt och en referens till cellen som motsvarar den tappade raden. Vi frågar checkedItems för index för motsvarande objekt genom att åberopa index för:). Denna metod returnerar en valfri int. Om checkedItems innehåller Artikel, vi tar bort det från checkedItems och ställ in cellens tillbehörstyp till .ingen. Om checkedItems innehåller inte Artikel, vi lägger till den till checkedItems och ställ in cellens tillbehörstyp till .bock.

Med dessa tillägg kan användaren nu markera objekt som gjort. Bygg och kör programmet för att se till att allt fungerar som förväntat.

3. Spara stat

Ansökan sparar inte tillstånd mellan lanseringar. För att lösa detta ska vi lagra objekt och checkedItems arrays i programmets användarinställningsdatabas.

Steg 1: Hämtar stat

Börja med att skapa två hjälpar metoder, loadItems () och loadCheckedItems (). Notera privat Nyckelord prefix varje hjälpar metod. De privat sökord berättar Swift att dessa metoder endast är tillgängliga från inom ViewController klass.

// MARK: Privata hjälpar metoder privata func loadItems () let userDefaults = UserDefaults.standard om låter objekt = userDefaults.object (forKey: "items") som? [String] self.items = items privat func loadCheckedItems () let userDefaults = UserDefaults.standard om låt checkedItems = userDefaults.object (forKey: "checkedItems") som? [String] self.checkedItems = checkedItems

De privat sökord är en del av swift s åtkomstkontroll. Som namnet antyder definierar åtkomstkontroll vilken kod som har åtkomst till vilken kod. Åtkomstnivåer gäller metoder, funktioner, typer etc. Apple refererar bara till enheter. Det finns fem åtkomstnivåer: öppna, offentliga, interna, fil-privata och privata.

  • Öppna / Public: Entiteter markerade som öppna eller offentliga är tillgängliga av enheter definierade i samma modul samt andra moduler. Detta är idealiskt för att exponera gränssnittet för en ram. Det finns flera skillnader mellan öppna och offentliga åtkomstnivåer. Du kan läsa mer om dessa skillnader i The Swift Programming Language.
  • Inre: Det här är standardåtkomstnivån. Med andra ord, om ingen åtkomstnivå anges, gäller denna åtkomstnivå. En enhet med en åtkomstnivå för internt är endast tillgängligt av enheter definierade i samma modul.
  • Fil-Private: En enhet som deklareras som fil-privat är endast tillgänglig av enheter definierade i samma källfil. Till exempel de privata hjälpar metoder som definieras i ViewController klassen är endast tillgänglig av ViewController klass.
  • Privat: Privat är mycket lik fil-privat. Den enda skillnaden är att en enhet som deklareras som privat endast är tillgänglig från den deklaration som den bifogas av. Till exempel, om vi skapar en förlängning för ViewController klass i ViewController.swift, Alla enheter som är markerade som fil-privata skulle inte vara tillgängliga i tillägget, men privata enheter skulle vara tillgängliga.

Implementeringen av hjälpar metoder är enkel om du är bekant med UserDefaults klass. För enkel användning, lagrar vi en referens till standardvärdet för standardinställningar i en konstant namngiven userDefaults. I fallet med loadItems (), vi frågar userDefaults för objektet som är associerat med nyckeln "objekt" och sänka den till ett valfritt utbud av strängar. Vi sparar säkert valfri, vilket innebär att vi lagrar värdet i konstanten objekt om valfri inte är noll, och ange värdet till objekt egenskapskontrollenheten.

Om om uttalandet ser förvirrande ut, sedan ta en titt på en enklare version av loadItems () metod i följande exempel. Resultatet är identiskt; Den enda skillnaden är korthet.

privata func loadItems () låt userDefaults = UserDefaults.standard låt storageItems = userDefaults.object (forKey: "items") som? [String] om låt poster = lagradeItems self.items = items

Genomförandet av loadCheckedItems () är identisk med undantag för nyckeln som används för att ladda objektet som är lagrat i databasens standardinställningar. Låt oss sätta loadItems () och loadCheckedItems () att använda genom att uppdatera viewDidLoad () metod.

åsidosätta func viewDidLoad () super.viewDidLoad () // Set Titel title = "Att göra" // Populera artiklar objekt = ["Köp mjölk", "Slutför självstudie", "Spela Minecraft"] // Ladda State loadItems loadCheckedItems () // Registrera Klass för Cellåtervinning tableView.register (UITableViewCell.self, forCellReuseIdentifier: "TableViewCell")

Steg 2: Spara stat

För att rädda staten implementerar vi ytterligare två privata hjälpar metoder, saveitems () och saveCheckedItems (). Logiken liknar den för loadItems () och loadCheckedItems (). Skillnaden är att vi lagrar data i databasens standardinställningar. Kontrollera att knapparna som används i setObject (_: forKey :) samtal matchar de som används loadItems () och loadCheckedItems ().

privat func saveItems () let userDefaults = UserDefaults.standard // Uppdatera användarinställningar userDefaults.set (items, forKey: "items") userDefaults.synchronize () privat func saveCheckedItems () let userDefaults = UserDefaults.standard // Uppdatering Användarinställningar userDefaults.set (checkedItems, forKey: "checkedItems") userDefaults.synchronize ()

De synkronisera() samtal är inte strikt nödvändigt. Operativsystemet ser till att data som du lagrar i databasens standard är skrivet till disken vid något tillfälle. Genom att åberopa synkronisera(), Men du säger explicit att operativsystemet skriver några väntande ändringar på disken. Detta är användbart under utveckling, eftersom operativsystemet inte skriver dina ändringar till disken om du dödar programmet. Det kan då verka som om något inte fungerar ordentligt.

Vi måste åberopa saveitems () och saveCheckedItems () på ett antal platser. För att börja ringa saveitems () när ett nytt objekt läggs till i listan. Vi gör detta i delegationsmetoden för AddItemViewControllerDelegate protokoll.

// MARK: Lägg till objekt Visa Controller Delegate Methods func controller (_ controller: AddItemViewController, didAddItem: String) // Uppdatera datakälla items.append (didAddItem) // Spara State saveItems () // Uppdatera tabellvy tableView.reloadData ( ) // Avvisa Lägg till föremål Visa Controller dismiss (animated: true)

När tillståndet för ett objekt ändras i Tableview (_: didSelectRowAt :), vi uppdaterar checkedItems. Det är en bra idé att också åberopa saveCheckedItems () vid det tillfället.

// MARK: - Tabellvisning Delegerade metoder func tableView (_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) tableView.deselectRow (vid: indexPath, animated: true) // Hämta objektlåtobjekt = objekt [indexPath.row] // Hämta cellen låt cellen = tableView.cellForRow (vid: indexPath) // Hitta index över objekt let index = checkedItems.index (av: objekt) om låt index = index checkedItems.remove (vid: index) cell? .AccessoryType =. ingen annan checkedItems.append (item) cell? .accessoryType = .checkmark // Spara stat sparaCheckedItems ()

När ett objekt raderas, båda objekt och checkedItems uppdateras. För att rädda denna ändring, ringer vi båda saveitems () och saveCheckedItems ().

func tableView (_ tableView: UITableView, commit redigeringStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) om redigeringStyle == .delete // Hämta objektlåt objekt = objekt [indexPath.row] // Uppdatera objekt items.remove (vid: indexPath .row) om låt index = checkedItems.index (av: item) checkedItems.remove (på: index) // Uppdatera tabell Visa tableView.deleteRows (vid: [indexPath], with: .right) // Spara State SaveItems () sparaCheckedItems ()

Det är allt. Bygg och kör programmet för att testa ditt arbete. Spela med programmet och tvinga sluta det. När du startar programmet igen ska det senast kända tillståndet laddas och synas.

4. Fastighetsobservatörer

Applikationens användarupplevelse är lite bristfällig just nu. När varje objekt raderas eller när programmet startas för första gången ser användaren en tom tabellvy. Detta är inte bra. Vi kan lösa detta genom att visa ett meddelande när det inte finns några föremål. Detta ger mig också möjlighet att visa dig en annan funktion hos Swift, fastighetsobservatörer.

Steg 1: Lägga till en etikett

Låt oss börja med att lägga till en etikett i användargränssnittet för att visa meddelandet. Förklara ett utlopp som heter messageLabel av typ UILabel i ViewController klass, öppen Main.storyboard, och lägg till en etikett i vyens kontroll.

@IBOutlet var messageLabel: UILabel!

Lägg till nödvändiga layoutbegränsningar på etiketten och anslut den med bildkontrollen messageLabel utlopp i Anslutningsinspektör. Ange etikettens text till Du har ingen dosering. och centrera etikettens text i Attribut Inspector.

Steg 2: Implementera en Objekt Observera

Meddelandemärket ska bara vara synligt om objekt innehåller inga element. När det händer bör vi också dölja tabellvyn. Vi kunde lösa detta problem genom att lägga till olika kontroller i ViewController klass, men ett mer bekvämt och elegant tillvägagångssätt är att använda en fastighetsobservatör.

Som namnet antyder observerar fastighetsobservatörer en egendom. En egenskapsobservatör åberopas när en egenskap ändras, även om det nya värdet är detsamma som det gamla värdet. Det finns två typer av fastighetsobservatörer.

  • kommer sätta: påkallad innan värdet har ändrats
  • didSet: åberopas efter att värdet har ändrats

För vårt syfte kommer vi att genomföra didSet observatör för objekt fast egendom. Ta en titt på syntaxen i följande kodbit.

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

Konstruktionen kan se lite udda först, så låt mig förklara vad som händer. När didSet egenskapsobservatör åberopas, efter objekt fastigheten har förändrats, vi kontrollerar om objekt egendom innehåller några element. Baserat på värdet av hasItems konstant, vi uppdaterar användargränssnittet. Så enkelt är det.

De didSet observatören överförs en konstant parameter som innehåller värdet av egenskapens gamla värde. Det utelämnas i ovanstående exempel, eftersom vi inte behöver det i vår implementering. Följande exempel visar hur det kan användas.

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

De oldValue parametern i exemplet har ingen explicit typ, eftersom Swift känner till typen av objekt fast egendom. I exemplet uppdaterar vi bara användargränssnittet om det gamla värdet skiljer sig från det nya värdet.

en kommer sätta observatör fungerar på ett liknande sätt. Huvudskillnaden är att parametern övergår till kommer sätta observatör är en konstant innehav av det nya värdet av fastigheten. När du använder fastighetsobservatörer, kom ihåg att de inte åberopas när instansen initieras.

Bygg och kör programmet för att se till att allt är anslutet korrekt. Även om programmet inte är perfekt och kan använda några fler funktioner, har du skapat ditt första iOS-program med Swift.

Slutsats

Under de senaste tre lektionerna i denna serie skapade du ett funktionellt iOS-program med Swifts objektorienterade funktioner. Om du har någon erfarenhet av programmering och utveckling av applikationer, måste du ha märkt att den nuvarande datamodellen har några brister, för att uttrycka det lätt.

Om du lagrar objekt som strängar och skapar en separat matris för att lagra ett objekts tillstånd, är det inte en bra idé om du bygger en riktig applikation. Ett bättre tillvägagångssätt skulle vara att skapa en separat Att göra klass för modellering av objekt och lagra dem i programmets sandlåda. Det är vårt mål för nästa lektion i serien.

Under tiden, kolla in några av våra andra kurser och handledning om Swift-språk iOS-utveckling!