Wykorzystanie Core Data i SQLite do przechowywania danych.
Poradnik wykorzystanie CoreData do przechowywania danych.
W poradniku opiszę jak utworzyć aplikację dla iPhone przechowującą loginy i hasła. Aplikacja będzie używać Core Data do przechowywania danych i zostanie napisana przy użyciu języka Swift. Czas wykonania 1-2 godziny.
Funkcjonalnie aplikacja będzie zapewniała podstawową funkcjonalność:
- Wyświetlanie listy nazwa i loginów.
- Wprowadzanie przez użytkownika nowego wpisu z hasłem.
- Umożliwi edycję wpisu oraz jego skasowanie.
Tworzenie projektu
Utwórz nowy projekt uruchamiając XCode i wybierając File -> New -> Project, zaznacz Single View Application.
Wprowadź nazwę, wybierz język Swift i zaznacz “Use Core Data”.
Interfejs użytkownika
W pierwszej kolejności usuwamy plik ViewController.swift, nie będzie on nam potrzebny.
Tworzymy nowy plik kontrolera MVC który będziemy używać do wyświetlenia listy.
Klasę nazywamy PasswordSaverTableViewController, dziedziczy ona po UITableViewController.
Usuwamy wszystkie metody z nowo utworzonej klasy, zostawiając tylko jej definicję, kod powinien wyglądać następująco:
1 2 3 4 5 |
import UIKit class PasswordSaverTableViewController: UITableViewController { } |
Dodanie kontrolera do storyboard.
Przechodzimy do Main.storyboard, w którym usuwamy nieużywany ViewController.
W jego miejsce dodajemy Table View Controller.
Należy zaznaczyć, że nasz nowy kontroler będzie startowym, więc zaznaczamy opcję Is Initial View Controller.
W celu podpięcia klasy PasswordSaverTableViewController do naszego Table View Controller przechodzimy do Custom Class i wybieramy klasę PasswordSaverTableViewController.
Następnie obudowujemy nasz kontroler w Navigation Controller. Można to prosto wykonać wybierając menu Editor menu -> Embed In -> Navigation Controller.
Dodanie przycisku dodawania do panelu nawigacji
W naszej aplikacji będziemy dodawać nowe hasła poprzez kliknięcie przycisku z symbolem plus. W tym celu dodajemy nowy button bar, w którym zmieniamy identyfikator na Add. Możemy zmienić tytuł panelu nawigacji na Password Saver.
Dodanie formularza do dodawania nowych haseł.
Dodajemy nową klasę DetailsViewController, dziedziczącą po UIViewController. Usuwamy z niej zbędny kod. Możemy teraz do storyboard dodać nowy formatkę View Controller i podpiąć nowo utworzoną klasę.
Teraz już możemy utworzyć przejście z formularza tabeli z naszymi wpisami do formularza dodawania nowego wpisu. W tym celu tworzymy nowy Action segue. Z wciśniętym przyciskiem ctrl przeciągamy z symbolu plusa do formularza dodawania. Nowy segue nazywamy „add”
W formualarzu dodajemy Navigation item, gdzie umieszczamy przyciski Cancel, Save oraz tytuł „Password details”
Pola edycji na formularzu
Nasz aplikacja będzie mogła zapisywać trzy pola: nazwę, login oraz hasło. Hasło będzie polem które będzie pokazywało gwiazdki, więc w celu ich odczytania dodamy przełącznik pokarz hasło.
W tym celu dodajemy trzy pola tekstowe. Poniżej dodajemy jedno pole Label oraz kontrolkę switch.
Kontrolkę switch przestawiamy w pozycję wyłączona.
Nasze pola musimy jeszcze wyrównać między sobą, oraz umieścić je w przestrzeni. Należy w tym celu wykonać wszystkie czynności pokazane na zdjęciu poniżej. Wyrównywanie wykonuje się z przyciskiem ctrl.
W celu zakończenia wyrównywania musimy jeszcze rozwiązać ew. problemy klikając Resolve Auto Loyout Issues -> Update Frame. W tym samym miejscu można kliknąć Clear Constraint jeśli popełniliśmy błąd i chcemy zacząć od nowa.
Musimy tylko jeszcze ustawić nasze pole z hasłem tak, żeby pokazywało gwiazdki zamiast tekstu. W tym celu zaznaczymy opcję Secure Text Entry. Można dodać też tekst z opisem znaczenia pól w opcji Placeholder.
Łączenie pól formularza z kodem.
Kontrolki z formularza można połączyć z kodem na dwa sposoby. W pierwszym przypadku przeciągamy je z wciśniętym ctrl do klasy DetailsViewController, lub piszemy kod i łączymy je w Connection Inspector.
Otrzymujemy kod w klasie DetailsViewController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class DetailsViewController: UIViewController { @IBOutlet weak var txtName: UITextField! @IBOutlet weak var txtLogin: UITextField! @IBOutlet weak var txtPassword: UITextField! @IBAction func showPassword(sender: UISwitch) { } @IBAction func save(sender: AnyObject) { } @IBAction func cancel(sender: AnyObject) { } } |
Jeśli teraz uruchomimy nasz program w symulatorze, to zauważymy, że po wejściu do dodawania haseł nie możemy wyjść z formularza. Również nie pokazuje nam się hasło po kliknięciu na show password. Możemy teraz zmienić nasz kod, tak, żeby obie te funkcjonalności działały. Zmieniamy metody na poniższe:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@IBAction func showPassword(sender: UISwitch) { txtPassword.secureTextEntry = !sender.on } @IBAction func save(sender: AnyObject) { dismissView() } @IBAction func cancel(sender: AnyObject) { dismissView() } func dismissView() { navigationController?.popViewControllerAnimated(true) } |
Metoda dismissView zdejmuje nam z wierzchu obecny formularz i powraca do tabeli z hasłami.
Implementacja Core Data
W celu zaprojektowania naszej bazy danych otwieramy PasswordSaver.xcdatamodeld w którym dodajemy nowe Entity o nazwie Password. Do naszej encji dodajemy trzy atrybuty name, login, password, wszystkie typu string.
Następnie możemy do naszej encji wygenerować klasę modelu dziedziczącą po NSManagedObject. Klikamy Editor -> Create NSManagedObject Subclass. Należy jej od razu zmienić nazwę na nazwę naszego programu kropka nazwa encji.
Klasa Password powinna wyglądać następująco:
1 2 3 4 5 6 7 8 9 |
import Foundation import CoreData class Password: NSManagedObject { @NSManaged var name: String @NSManaged var login: String @NSManaged var password: String } |
Implementacja dodawani nowych rekordów
W klasie DetailsViewController na samej górze implementujemy kontekst dostępu do danych.
1 |
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext |
Ważne jest dodanie również klauzuli importowania biblioteki CoreData
1 |
import CoreData |
Dodajemy metodę służącą do dodawania nowych rekordów:
1 2 3 4 5 6 7 8 9 10 |
func addTask(){ let entityDescription = NSEntityDescription.entityForName("Password", inManagedObjectContext: managedObjectContext!) let passwordEntity = Password(entity: entityDescription!, insertIntoManagedObjectContext: managedObjectContext) passwordEntity.name = txtName.text passwordEntity.login = txtLogin.text passwordEntity.password = txtPassword.text managedObjectContext?.save(nil) } |
Oraz modyfikujemy metodą zapisującą:
1 2 3 4 |
@IBAction func save(sender: AnyObject) { addTask() dismissView() } |
Nasz klasa DetailsViewController powinna teraz wyglądać następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import UIKit import CoreData class DetailsViewController: UIViewController { let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext @IBOutlet weak var txtName: UITextField! @IBOutlet weak var txtLogin: UITextField! @IBOutlet weak var txtPassword: UITextField! @IBAction func showPassword(sender: UISwitch) { txtPassword.secureTextEntry = !sender.on } @IBAction func save(sender: AnyObject) { addTask() dismissView() } @IBAction func cancel(sender: AnyObject) { dismissView() } func dismissView() { navigationController?.popViewControllerAnimated(true) } func addTask(){ let entityDescription = NSEntityDescription.entityForName("Password", inManagedObjectContext: managedObjectContext!) let passwordEntity = Password(entity: entityDescription!, insertIntoManagedObjectContext: managedObjectContext) passwordEntity.name = txtName.text passwordEntity.login = txtLogin.text passwordEntity.password = txtPassword.text managedObjectContext?.save(nil) } } |
Wyświetlanie zapisanych rekordów
Możemy już dodawać nasze hasła do bazy SQLite, ale nie widzimy co tam zapisaliśmy. Przyszła pora wyświetlić nasze wpisy w tabeli. W celu pobierania danych i wyświetlania w tabeli posłużymy się klasą NSFetchedResultsController która ułatwi nam wyświetlanie danych. Przed przystąpienie do kodowania musimy zmienić styl komórki naszej tabeli i nadać jej unikalny identyfikator w celu rozpoznania jej przy re-użyciu komórki. Zmieniamy jej też oznaczenie dostępu na Detail disclosure.
Możemy teraz dodać do naszej klasy PasswordSaverTableViewController import biblioteki CoreData. Następnie implementujemy kontekst dostępu do danych oraz obiekt klasy fetchedResultsController który będzie nam pomagał wyświetlać dane. W klasie dodajemy kod:
1 2 3 4 5 6 7 8 |
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext var fetchedResultsController : NSFetchedResultsController = NSFetchedResultsController() func getFetchedResultsController() -> NSFetchedResultsController { var fetchedResultsController = NSFetchedResultsController(fetchRequest: taskFetchRequest(), managedObjectContext: managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil) return fetchedResultsController } |
Musimy również dodać metodę która będzie pobierała rekordy z encji:
1 2 3 4 5 6 7 |
func taskFetchRequest() -> NSFetchRequest { let fetchRequest = NSFetchRequest(entityName: "Password") let sortDescription = NSSortDescriptor(key: "name", ascending: true) fetchRequest.sortDescriptors = [sortDescription] return fetchRequest } |
Oraz metodę odświeżania danych:
1 2 3 4 |
func controllerDidChangeContent(Controller: NSFetchedResultsController) { tableView.reloadData() } |
Ponieważ nasze dane chcemy pobierać i odświeżać przy każdym wyświetleniu tabeli musimy zmodyfikować metodę viewDidLoad() w której ustawimy również delegata obiektu fetchedResultsController na instancję naszej klasy.
1 2 3 4 5 6 7 |
override func viewDidLoad() { super.viewDidLoad() fetchedResultsController = getFetchedResultsController() fetchedResultsController.delegate = self fetchedResultsController.performFetch(nil) } |
Jeśli teraz zbudujemy nasz projekt, otrzymamy błąd, ponieważ klasa PasswordSaverTableViewController nie jest delegatem typu NSFetchedResultsControllerDelegate. Należy dodać taką deklarację zmieniając linię class PasswordSaverTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
Nasza klasa powinna wyglądać następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import UIKit import CoreData class PasswordSaverTableViewController: UITableViewController, NSFetchedResultsControllerDelegate { let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext var fetchedResultsController : NSFetchedResultsController = NSFetchedResultsController() func getFetchedResultsController() -> NSFetchedResultsController { var fetchedResultsController = NSFetchedResultsController(fetchRequest: taskFetchRequest(), managedObjectContext: managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil) return fetchedResultsController } func taskFetchRequest() -> NSFetchRequest { let fetchRequest = NSFetchRequest(entityName: "Password") let sortDescription = NSSortDescriptor(key: "name", ascending: true) fetchRequest.sortDescriptors = [sortDescription] return fetchRequest } func controllerDidChangeContent(Controller: NSFetchedResultsController) { tableView.reloadData() } override func viewDidLoad() { super.viewDidLoad() fetchedResultsController = getFetchedResultsController() fetchedResultsController.delegate = self fetchedResultsController.performFetch(nil) } } |
Implementacja metod wyświetlania danych w tabeli.
Implementujemy po kolei metody:
Pobieranie ilości sekcji tabeli:
1 2 3 |
override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return fetchedResultsController.sections!.count } |
Pobieranie ilości rekordów w sekcji:
1 2 3 |
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return fetchedResultsController.sections![section].numberOfObjects } |
Pobieranie komórki naszej tabeli:
1 2 3 4 5 6 7 8 |
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell let passwordEntity = fetchedResultsController.objectAtIndexPath(indexPath) as! Password cell.textLabel!.text = passwordEntity.name cell.detailTextLabel!.text = passwordEntity.login return cell } |
Aktualnie nasz program będzie wyświetlał dane w tabeli, będziemy mogli również dodać nowy rekord.
Kasowanie danych
Kasowanie danych w tabeli jest bardzo proste, należy przeciągnąć komórkę tabeli w lewą stronę i wcisnąć Delete. W celu dodania opcji kasowania danych należy dodać metodę:
1 2 3 4 5 |
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { let managedObject: NSManagedObject = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject managedObjectContext?.deleteObject(managedObject) managedObjectContext?.save(nil) } |
Edycja danych
W celu edycji danych użyjemy ponownie formatki z detalami danych naszego hasła. W tym celu trzeba utworzyć segue z komórki do widoku detali. Wykonujemy to przytrzymując klawisz ctrl. Nowy segue nazwiemy edit w celu odróżnienia od akcji add.
Teraz musimy przygotować dane które będziemy edytować. W tym celu w klasie PasswordSaverTableViewController nadpisujemy metodę prepareForSegue:
1 2 3 4 5 6 7 8 9 10 |
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "edit" { let cell = sender as! UITableViewCell let indexPath = tableView.indexPathForCell(cell) let passwordEntity: Password = fetchedResultsController.objectAtIndexPath(indexPath!) as! Password let detailsController : DetailsViewController = segue.destinationViewController as! DetailsViewController detailsController.passwordModel = passwordEntity } } |
Pobiera ona jeden rekord z tabeli i przekazuje do modelu w obiekcie typu DetailsViewController. Musimy teraz zaimplementować nasz model, w tym celu przechodzimy do klasy DetailsViewController i dopisujemy na początku:
1 |
var passwordModel : Password? = nil |
Możemy również zaimplementować przekazywanie danych z modelu do pól tesktowych:
1 2 3 4 5 6 7 |
override func viewDidLoad() { if passwordModel != nil { txtName.text = passwordModel?.name txtLogin.text = passwordModel?.login txtPassword.text = passwordModel?.password } } |
Dodajemy również metodę zapisującą edytowane dane:
1 2 3 4 5 6 |
func editTask() { passwordModel?.name = txtName.text passwordModel?.login = txtLogin.text passwordModel?.password = txtPassword.text managedObjectContext?.save(nil) } |
I modyfikujemy naszą metodę zapisującą:
1 2 3 4 5 6 7 8 |
@IBAction func save(sender: AnyObject) { if passwordModel == nil { addTask() } else { editTask() } dismissView() } |
Na tym etapie nasz program ma możliwość edycji, dodawania i kasowania rekordów.
Zapraszam do zadawania pytań jeśli coś jest niejasne.