Туториал по фреймворку Realm

Если вы нашли опечатку в тексте, выделите ее и нажмите CTRL + ENTER.

Xcode: 
7.3
Swift: 
2.2

Realm - это решение для кросс-платформенной мобильной базы данных, разработанное специально для мобильных приложений.

Он быстрый, легкий, его очень просто интегрировать в свой проект. Наиболее часто используемые функции, такие как запросы к базе данных, состоят из одной строчки кода!

В отличие от оболочек вокруг Core Data, таких как MagicalRecord, Realm не зависит от Core Data или даже от SQLite бэкэнда. Разработчики Realm утверждают, что их решение для хранения данных быстрее, чем даже SQLite и Core Data.

Вот пример кода Core Data для извлечения набора записей с предикатом, а затем сортировки результатов:

let fetchRequest = NSFetchRequest(entityName: "Specimen")
let predicate = NSPredicate(format: "name BEGINSWITH [c]%@", searchString)
fetchRequest.predicate = predicate
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
 
do {
  let results = try managedObjectContext?.executeFetchRequest(fetchRequest)
} catch { ... }

То, что занимает довольно много строк кода с использованием Core Data, может быть достигнуто гораздо меньшим количеством строк в Realm:

let predicate = NSPredicate(format: "name BEGINSWITH [c]%@", searchString);
 
do {
  let specimens = try Realm().objects(Specimen).filter(predicate).sorted("name", ascending: true)
} catch { ... }

Преимуществом работы с более сжатым кодом Realm является то, что писать и читать ваш код становится гораздо проще.

Этот туториал по Realm познакомит вас с основными функциями Realm на iOS. Вы узнаете, как начать работать с фреймворком Realm, создавать модели, выполнять запросы и обновлять записи.

Поехали!

Вот вам сценарий: вас приняли на работу в качестве стажера в National Park Service (Службу Национальных Парков), и ваша работа состоит в документировании видов, обитающих в самых больших национальных парках в Соединенных Штатах. Вам нужен помощник для хранения ваших заметок и документировании выводов, но агентство не может вам его предоставить из-за экономии. Вместо этого, вы решаете создать виртуального помощника себе сами - приложение под названием “Agents Partner” ( "Партнерское агенство”).

Загрузите стартовый проект этого туториала здесь: AgentsPartner_Starter.

Откройте стартовый проект в Xcode. MapKit в вашем проекте уже установлен. На этом этапе ваше приложение содержит только экземпляры UITableView и MKMapView для обеспечения работоспособности используемых карт.

Заметка

Если вы хотите получить дополнительную информацию о MapKit, начните с Копошилка: MapKit, где есть дополнительная информация по работе с MapKit.

Стартовый проект не содержит Realm, так что пришло время его добавить.

Заметка

Данный туториал был написан с Realm 0.98.0

Отличный способ установить Realm - это сделать это с CocoaPods. CocoaPods является менеджером зависимостей в проектах Cocoa на Swift и Objective-C, он содержит тысячи библиотек, которые вы можете скачать и использовать в собственных проектах.

Заметка

Если вы не знакомы с CocoaPods или вам необходима помощь с его установкой, то Вы можете посмотреть пример работы с CocoaPods в видео при работе со сторонней библиотекой SwiftyJSON.

Создайте файл с именем 'Podfile' в корневой директории стартового проекта (Для этого в командной строке просто используйте команду touch Podfile). Скопируйте следующий фрагмент текста и вставьте его в Podfile:

platform :ios, ‘9.0’
use_frameworks!
 
target ‘Agents Partner’ do
pod 'RealmSwift', '~> 0.98'
end

Сохраните и закройте ваш Podfile. Затем снова в командной строке, в корневом каталоге вашего проекта (то же местоположение, в котором находится ваш Podfile), запустите команду pod install. Она сообщает CocoaPods, что нужно просканировать Podfile и установить модули, которые перечислены в вашем Podfile. Вот так! Установка Realm может занять несколько минут, и как только она будет завершена, вы увидите строку в нижней части экрана, начинающуюся с Pod installation complete!

Откройте корневой каталог стартового проекта в Finder, и в нем вы увидите папки, которые разместил здесь CocoaPods, в дополнение к Agents Partner.xcworkspace. Если Ваш стартовый проект Agents Partner уже открыт в Xcode, закройте его, а затем двойным щелчком откройте файл .xcworkspace. Именно этот файл вы будете открывать далее при работе над этим проектом. Если вы по ошибке откроете обычный файл проекта, Xcod не сможет найти какие-либо dependencies, установленные вами через CocoaPods, так что вы должны использовать файл .xcworkspace. Разверните проект Agents Partner в Project navigator, а затем и группу/ папку, также называемую Agents Partner для того, чтобы увидеть файлы, с которыми вы будете работать.

Вот так! Запустите проект, чтобы удостоверится, что все компилируется. Если нет, то проверьте еще раз правильно ли вы сделали описанные выше шаги. Вы должны увидеть основной экран:

Браузер Realm'а

У Realm также есть хорошая утилита, и вам нужно ее установить из App Store, она сделает вашу жизнь немного легче.

Realm Browser позволяет читать и редактировать базы данных Realm. Это вам сильно поможет при разработке, так как формат базы данных Realm является их собственностью и его трудно «прочитать» человеку. Скачать его здесь.

Концепции и основные классы

Для того, чтобы лучше понять, что именно делает Realm, вот обзор классов Realm и понятий, которые вы будете использовать в этом уроке:

Realm: экземпляры Realm - это сердце фреймворка, это ваша точка доступа к основной базе данных, аналогично управляемому контексту объекта в Core Data. Вы будете создавать экземпляры при помощи инициализатора Realm().

Object (Объект): Это ваша модель Realm. Процесс создания модели определяет схему базы данных. Для того, чтобы создать модель вы просто создаете подкласс Object и определяете fields (поля), которые вы хотите сохранить как свойства.

Relationships (Связи или отношения): Вы создаете связь one-to-many (один ко многим) между объектами, просто объявляя свойство типа Object, на которое вы хотите ссылаться. Вы можете создать связь многие-к-одному и многие-ко-многим через свойство типа List, что приводит вас к …

Write Transactions (Запись операций): Любые операции в базе данных, такие как создание, редактирование или удаление объектов, должны выполняться в рамках операций writes, которые происходят с помощью вызова write(_:) у экземляров Realm.

Queries (Запросы): Для извлечения объектов из базы данных вам нужно использовать запросы. Самая простая форма запроса - это вызов objects() у экземпляра Realm, передавая класс Object, который вы ищете. Если ваши поисковые запросы являются более сложными, вы можете использовать предикаты, выстраивая запросы, и также организуя результаты поиска.

Results (Результаты): Результаты - это автоматически обновляемый тип контейнера, который вы получаете в ответ на объектные запросы. У них много общего с обычными массивами, в том числе синтаксис сабскрипта для захвата элемента в индексе.

Теперь, когда вы познакомились с Realm, пришло время взяться за остальную часть проекта этого туториала.

Создание первой модели

Откройте Specimen.swift из группы Models и добавьте следующую реализацию:

import Foundation
import RealmSwift
 
class Specimen: Object {
  dynamic var name = ""
  dynamic var specimenDescription = ""
  dynamic var latitude = 0.0
  dynamic var longitude = 0.0
  dynamic var created = NSDate()
}

Этот код добавляет несколько свойств: name и specimenDescription, хранящий имя и описание вида. Конкретные типы данных в Realm, такие как строки, должны быть инициализированы со значением. В этом случае вы инициализируете их с пустой строкой.

latitude (широта) и longitude (долгота) содержат координаты вида. Здесь вы устанавливаете тип на Double и инициализируете их с 0.0.

И, наконец, created хранит дату создания экземпляра. NSDate() возвращает текущую дату, так что вы можете инициализировать свойство с этим значением.

Теперь, когда вы создали свою первую модель в Realm, как вы смотрите на то, чтобы попытаться реализовать полученные знания?

Экземпляры будут разделены на различные категории. Задача состоит в том, чтобы самостоятельно создать модель Category, задать имя файлу Category.swift и задать вашей новой модели только один String свойства name.

Это будет реализовано так:

import Foundation
import RealmSwift
 
class Category : Object {
  dynamic var name = ""
}

Теперь у вас есть модель Category, которую вам нужно связать каким-то образом с моделью Specimen.

Вспомним, что в примечании выше сказано, что вы можете создать связи между моделями, просто объявив свойства с соответствующей моделью, с которой нужна связь.

Откройте Specimen.swift и добавьте следующее объявление ниже других свойств:

dynamic var category: Category!

Это устанавливает связь один-ко-многим между Specimen и Category. Это означает, что каждый Specimen может принадлежать только к одной Category, но каждая Category может иметь много Specimen.

Теперь, когда ваши основные модели данных на месте - пришло время добавить записи в базу данных!

Добавление записей

Когда пользователь добавляет новый вид, то они могут войти в имя вида и выбрать ему категорию. Откройте CategoriesTableViewController.swift. Во вью контроллере будет список категорий в виде таблицы, чтобы пользователь мог выбрать.

Перед тем как начать писать код для того, чтобы интегрировать Realm в этот вью контроллер, необходимо сначала импортировать фрейворк RealmSwift в этот исходный файл. Добавьте следующую строчку в верхнюю часть файла, чуть ниже import UIKit

import RealmSwift

Вам нужно заполнить этот table view некоторыми дефолтными категориями. Вы можете хранить эти экземпляры Category в экземпляре Results.

У CategoriesTableViewController есть массив categories, использующийся на данном этапе как массив-плейсхолдер. Найдите следующий код в верхней части определения класса:

var categories = []

и замените его следующими строчками:

let realm = try! Realm()
lazy var categories: Results<Category> = { self.realm.objects(Category) }()

Когда вам нужно получить объекты, вы должны определить, какие модели вы хотите получить. В приведенном выше коде сначала создается экземпляр Realm, а затем заполняется categories через вызов objects(_:), передавая имени класса тип модели, который вам нужен.

Заметка

 

Для упрощения кода, необходимого для этого туториала, вы будете использовать try! при вызове методов Realm, которые будут выбрасывать ошибку. Когда вы будете писать код самостоятельно, вам нужно будет писать try и do/catch для вылавливания ошибок и соответствующим образом их обрабатывать.

Вам нужно дать пользователю несколько категорий по умолчанию, чтобы он мог из них выбирать, первый раз запуская приложение.

Добавьте следующий вспомогательный метод для определения класса ниже preferredStatusBarStyle:

func populateDefaultCategories() {
 
  if categories.count == 0 { // 1
 
     try! realm.write() { // 2
 
      let defaultCategories = ["Birds", "Mammals", "Flora", "Reptiles", "Arachnids" ] // 3
 
      for category in defaultCategories { // 4
        let newCategory = Category()
        newCategory.name = category
        self.realm.add(newCategory)
      }
    }
 
    categories = realm.objects(Category) // 5
  }
}

Рассмотрим пошагово:

  1. Если count здесь равно 0 - это означает, что база данных не имеет записей Category, как в случае, когда вы запускаете приложение в первый раз.
  2. Это часть начинает транзакцию с realm - теперь вы готовы добавить записи в базу данных.
  3. Здесь вы создаете список дефолтных имен категорий и затем проводите через них итерацию.
  4. Для каждого названия категории, вы создаете новый экземпляр Category, заполняете name и добавляете объект в realm.
  5. И, наконец, вы запрашиваете все категории, которые вы создали и храните их в categories.

Добавьте следующую строчку в конец viewDidLoad():

populateDefaultCategories()

Это вызывает вспомогательный метод для заполнения тестовых категорий, когда загружается вью.

Теперь, когда у вас есть данные, вам необходимо обновить методы источника данных тейбл вью для отображения категорий. Найдите tableView(_:cellForRowAtIndexPath:)  и замените метод следующей реализацией:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCellWithIdentifier("CategoryCell", forIndexPath: indexPath)
 
  let category = categories[indexPath.row]
  cell.textLabel?.text = category.name
 
  return cell
}

Эта реализация извлекает категорию из categories по индексу indexPath.row, а затем устанавливает значение ярлыка ячейки, чтобы показать свойство name категории.

Добавьте это свойство ниже других свойств, только что добавленных вами в CategoriesTableViewController:

var selectedCategory: Category!

Вы будете использовать это свойство для хранения выбранной Category.

Найдите tableView(_:willSelectRowAtIndexPath:) и замените весь метод следующим текстом:

override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath {
  selectedCategory = categories[indexPath.row]
  return indexPath
}

Теперь это будет хранить выбор пользователя в свойстве, которое вы объявили выше.

Запустите приложение.

Увеличьте масштаб и выберете на карте что-то интересное, создайте новую аннотацию, нажав на кнопку + в верхнем правом углу. Нажмите на иконку на карте, чтобы выбрать позицию, а затем нажмите на данные аннотаций для внесения изменений. Затем нажмите на текстовое поле Category, чтобы увидеть список категорий, как показано ниже:

Вы можете выбрать категорию, но она будет только хранить его в свойстве, а не где-то в базе данных. То, что вы видите отображение категорий в приложении - это хорошо, но нам нужно увидеть записи в базе данных. Вы можете сделать это с помощью Realm Browser.

Работа с Realm Browser

На этом этапе что вы не знаете, так это где “живет” ваша база данных Realm. С помощью небольшой уловки вы сможете ее найти.

Откройте MapViewController.swift и импортируйте фреймворк RealmSwift. Добавьте следующую строчку в верхнюю часть файла, чуть ниже существующих выражений import в верхней части файла:

import RealmSwift

Теперь добавьте следующую строчку в viewDidLoad() сразу после вызова super.viewDidLoad()

print(Realm.Configuration.defaultConfiguration.path!)

Эта строчка просто указывает расположение базы данных в консоли дебаггера. После этого небольшого шага, загружаем базу данных с помощью Realm Browser.

Запустите приложение; вы увидите, что оно сообщит вам местонахождение базы данных в консоли Xcode.

Самый простой путь к папке с базой данных - это открыть Finder, нажать Cmd-Shift-G  и вставить тот путь, который вам сообщит приложение.

После того, как вы откроете папку в Finder, вы увидите там один или два файла. Один из них default.realm - это ваш файл с базой данных. Второго файла возможно и не будет, это default.realm.lock, который не дает видоизменяться базе данных из-за других приложений, в то время когда она используется.

Если вы еще не загрузили Realm Browser, скачать его прямо сейчас из Mac App Store. Двойной клик на default.realm откроет его через Realm Браузер:

После того, как база данных будет открыта в Realm браузере, вы увидите Category с цифрой 5 напротив. Это означает, что класс содержит пять записей. Выберите класс для проверки отдельных полей, которые содержаться внутри.

Добавление категорий

Теперь вы можете реализовать логику, установив category Specimen.

Откройте AddNewEntryController.swift и импортируйте фреймворк RealmSwift. Еще раз импортируйте Realm в верхней части файла, чуть ниже существующих выражений импорта:

import RealmSwift

Теперь добавьте следующее свойство к классу:

var selectedCategory: Category!

Вы будете использовать эту функцию для сохранения выбранной категории.

Затем, найдите unwindFromCategories()  и добавьте следующую реализацию:

if segue.identifier == "CategorySelectedSegue" {
  let categoriesController = segue.sourceViewController as! CategoriesTableViewController
  selectedCategory = categoriesController.selectedCategory
  categoryTextField.text = selectedCategory.name
}

unwindFromCategories()  вызывается, когда пользователь выбирает категорию из CategoriesTableViewController, которую вы создали ранее. Здесь вы получаете выбранную категорию, сохраняете ее локально в selectedCategory, а затем заполняете текстовое поле с названием категории.

Теперь, когда вы позаботились о категориях, можно создать свой первый Specimen!

Там же в AddNewEntryController.swift, добавьте еще одно свойство к классу:

var specimen: Specimen!

Это свойство будет хранить новый объект вида.

Затем добавьте вспомогательный метод классу ниже:

func addNewSpecimen() {
  let realm = try! Realm() // 1
 
  try! realm.write { // 2
    let newSpecimen = Specimen() // 3
 
    newSpecimen.name = self.nameTextField.text! // 4
    newSpecimen.category = self.selectedCategory
    newSpecimen.specimenDescription = self.descriptionTextField.text
    newSpecimen.latitude = self.selectedAnnotation.coordinate.latitude
    newSpecimen.longitude = self.selectedAnnotation.coordinate.longitude
 
    realm.add(newSpecimen) // 5
    self.specimen = newSpecimen // 6
  }
}

Вот что делает код:

  1. Вы сначала получаете экземпляр Realm, как и раньше.
  2. Здесь вы начинаете транзакцию записи, чтобы добавить новый Specimen.
  3. Затем создается новый образец Specimen.
  4. Затем присваиваете значения Specimen. Значения поступают из полей ввода текста в пользовательском интерфейсе, выбранные категории, а также координаты с аннотаций на карте.
  5. Затем вы добавляете новые Specimen в Realm.
  6. И, наконец, вы назначаете новый Specimen вашему свойству specimen.

Вам будет нужен какой-то валидатор, чтобы убедиться, что все поля в вашем Specimen заполняются правильно. Метод validateFields() в AddNewEntryController

существует именно для этого, то есть он проверяет имя вида и описание. Так как вы только что добавили возможность назначать категорию виду, значит вам нужно также проверять и поле категории тоже.

Там же в AddNewEntryController.swift, найдите строку в validateFields(), которая выглядит следующим образом:

if nameTextField.text!.isEmpty || descriptionTextField.text!.isEmpty {

Измените эту строку следующим образом:

if nameTextField.text!.isEmpty || descriptionTextField.text!.isEmpty || selectedCategory == nil {

Это подтверждает, что все поля были заполнены, и что вы также выбрали категорию.

Затем добавьте следующий метод классу:

override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool {
  if validateFields() {
    addNewSpecimen()
    return true
  } else {
    return false
  }
}

В приведенном выше коде, вы вызываете метод для проверки полей, и только в том случае, если все заполнено, вы добавляете новый образец.

Запустите приложение, нажмите кнопку +, чтобы создать новый Specimen. Введите имя и описание, выберите Category и нажмите Confirm, чтобы добавить Specimen в базу данных.

Вью контроллер освобождается, но ничего не происходит. В чем дело?

Да - вы разместили запись в вашем Realm, но вы еще не отметили на карте вновь созданный вид!

Получение записей

Теперь, когда вы добавили вид в базу данных, вам нужно, чтобы он появился на карте.

Во-первых, еще раз взгляните на обновленную базу данных в Realm Browser:

Вы видите ваш вид, со всеми заполненными полями, а также широту и долготу из MKAnnotation. Вы также увидите ссылку на категорию вашего вида - что означает, что ваша связь Category “один-ко-многим” работает, как и ожидалось. Выберите Category в вашей записи Specimen для просмотра самой записи Category.

Теперь вам нужно заполнить карту в приложении.

Откройте SpecimenAnnotation.swift и добавьте свойство к классу:

var specimen: Specimen?

Это будет держать образец для аннотации.

Затем замените инициализатор следующим текстом:

init(coordinate: CLLocationCoordinate2D, title: String, subtitle: String, specimen: Specimen? = nil) {
  self.coordinate = coordinate
  self.title = title
  self.subtitle = subtitle
  self.specimen = specimen
}

Изменение заключается в том, что мы добавили возможность передачи Specimen. Вид будет иметь значение по умолчанию nil, что означает, что вы, если хотите, можете опустить этот аргумент. Это означает, что остальная часть приложения может продолжать вызывать инициализатор только с первыми тремя аргументами, как это и обычно и происходит без аргумента вида.

Теперь откройте MapViewController.swift, добавьте новое свойство классу:

var specimens = try! Realm().objects(Specimen)

Так как вы хотите сохранить коллекцию образцов в этом свойстве, вы просто запрашиваете экземпляр Realm для всех объектов типа Specimen.

Теперь вам будет нужен какой-то механизм для заполнения карты. Там же в MapViewController.swift добавьте следующий метод классу:

func populateMap() {
  mapView.removeAnnotations(mapView.annotations) // 1
 
  specimens = try! Realm().objects(Specimen) // 2
 
  // Create annotations for each one
  for specimen in specimens { // 3
    let coord = CLLocationCoordinate2D(latitude: specimen.latitude, longitude: specimen.longitude);
    let specimenAnnotation = SpecimenAnnotation(coordinate: coord,
      title: specimen.name,
      subtitle: specimen.category.name,
      specimen: specimen)
    mapView.addAnnotation(specimenAnnotation) // 4
  }
}

Рассмотрим пошагово:

  1. Во-первых, удалите все существующие аннотации на карте, чтобы начать с чистого листа.
  2. Далее, обнулите ваше свойство specimens.
  3. Затем вы перебираете specimens и создаете SpecimenAnnotation с координатами вида, его name и его category.
  4. И, наконец, вы добавляете каждый specimenAnnotation в MKMapView.

Теперь вам нужно откуда-то вызвать этот метод. Найдите viewDidLoad() и добавьте эту строку в конец реализации:

populateMap()

Это гарантирует то, что карта будет заполнена образцами всякий раз, когда загружается вью контроллер карты.

Наконец, вам нужно просто изменить вашу аннотацию и включить имя образца и категорию. Найдите unwindFromAddNewEntry(_:) и замените метод следующей реализацией:

@IBAction func unwindFromAddNewEntry(segue: UIStoryboardSegue) {
 
  let addNewEntryController = segue.sourceViewController as! AddNewEntryController
  let addedSpecimen = addNewEntryController.specimen
  let addedSpecimenCoordinate = CLLocationCoordinate2D(latitude: addedSpecimen.latitude, longitude: addedSpecimen.longitude)
 
  if let lastAnnotation = lastAnnotation {
    mapView.removeAnnotation(lastAnnotation)
  } else {
    for annotation in mapView.annotations {
      if let currentAnnotation = annotation as? SpecimenAnnotation {
        if currentAnnotation.coordinate.latitude == addedSpecimenCoordinate.latitude && currentAnnotation.coordinate.longitude == addedSpecimenCoordinate.longitude {
          mapView.removeAnnotation(currentAnnotation)
          break
        }
      }
    }
  }
 
  let annotation = SpecimenAnnotation(coordinate: addedSpecimenCoordinate, title: addedSpecimen.name, subtitle: addedSpecimen.category.name, specimen: addedSpecimen)
 
  mapView.addAnnotation(annotation)
  lastAnnotation = nil;
}

Этот метод вызывается тогда, когда вы только вернулись из AddNewEntryController и у вас есть новые образцы, которые нужно добавить на карту. При добавлении нового образца на карту, он получает шаблонную аннотационную иконку. Теперь, когда у вас есть категория, вы хотите заменить эту иконку на иконку конкретной категории. Вы просто удаляете последнюю аннотацию, добавленную на карту и заменяете ее на аннотацию, которая показывает имя и категорию вида.

Запустите приложение, создайте новые образцы различных категорий и посмотрите, как происходит обновление карт:

Другой ракурс

Вы, возможно, заметили кнопку Log в верхнем левом углу карты. В дополнение к карте, приложение также имеет table view в виде текстового списка всех аннотаций под названием Log View. Теперь вы будете заполнять этот table view данными.

Откройте LogViewController.swift и импортируйте снова RealmSwift ниже других выражений import:

import RealmSwift

Затем замените свойство specimens на следующее:

var specimens = try! Realm().objects(Specimen).sorted("name", ascending: true)

В приведенном выше коде, вы заменяете массив плейсхолдеров массивом Results, которые держат Specimen-ы так же, как вы это делали в MapViewController. Они будут отсортированы по name.

Затем замените tableView(_:cellForRowAtIndexPath:) следующей реализацией:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = self.tableView.dequeueReusableCellWithIdentifier("LogCell") as! LogCell
 
  let specimen = specimens[indexPath.row]
 
  cell.titleLabel.text = specimen.name
  cell.subtitleLabel.text = specimen.category.name
 
  switch specimen.category.name {
  case "Uncategorized":
    cell.iconImageView.image = UIImage(named: "IconUncategorized")
  case "Reptiles":
    cell.iconImageView.image = UIImage(named: "IconReptile")
  case "Flora":
    cell.iconImageView.image = UIImage(named: "IconFlora")
  case "Birds":
    cell.iconImageView.image = UIImage(named: "IconBird")
  case "Arachnid":
    cell.iconImageView.image = UIImage(named: "IconArachnid")
  case "Mammals":
    cell.iconImageView.image = UIImage(named: "IconMammal")
  default:
    cell.iconImageView.image = UIImage(named: "IconUncategorized")
  }
  return cell
}

Этот метод теперь будет заполнять ячейку с именем образца и категорию.

Запустите приложение. Нажмите Log и вы увидите все ваши введенные образцы в table view, вот как:

Получение с применением предикатов

Если вы действительно хотите, чтобы ваше приложение преуспело, вам понадобится удобная функция поиска. Ваш стартовый проект содержит экземпляр UISearchController - вам нужно просто добавить несколько модификаций, подходящих для вашего приложения, чтобы заставить его работать с Realm.

В LogViewController.swift замените свойство searchResults следующим текстом:

var searchResults = try! Realm().objects(Specimen)

Теперь добавьте метод классу ниже:

func filterResultsWithSearchString(searchString: String) {
  let predicate = NSPredicate(format: "name BEGINSWITH [c]%@", searchString) // 1
  let scopeIndex = searchController.searchBar.selectedScopeButtonIndex // 2
  let realm = try! Realm()
 
  switch scopeIndex {
  case 0:
    searchResults = realm.objects(Specimen).filter(predicate).sorted("name", ascending: true) // 3
  case 1:
    searchResults = realm.objects(Specimen).filter(predicate).sorted("created", ascending: true) // 4
  default:
    searchResults = realm.objects(Specimen).filter(predicate) // 5
  }
}

Вот для чего нужна данная функция:

  1. Сначала необходимо создать предикат, который осуществляет поиск name (имен), которые начинаются с searchString. [c], следующая за BEGINSWITH, указывает на поиск без учета регистра.
  2. Затем вы берете ссылку на индекс выделенной области видимости из поисковой строки. 
  3. Если выбрана первая кнопка на segmened control, то результаты будут отсортированы по названию в возрастающем порядке.
  4. Если выбрана вторая кнопка, отсортируйте результаты по дате создания по возрастанию.
  5. Если ни одна из кнопок не выбрана, то результаты сортироваться не будут, мы просто будем брать их в том порядке, в каком они вернулись из базы данных.

Теперь вам на самом деле нужно выполнить сортировку в момент, когда пользователь взаимодействует с полем поиска. В updateSearchResultsForSearchController(_:) добавьте следующие две строчки в начале метода:

let searchString = searchController.searchBar.text!
filterResultsWithSearchString(searchString)

Так как table view c результатами поиска вызывает те же методы, вам потребуется небольшое изменение в tableView(_:cellForRowAtIndexPath:) для обработки основного log table view и результатов поиска. В этом методе, найдите строку, которая присваивает значение в specimen:

let specimen = specimens[indexPath.row]

Удалите эту строку и замените ее следующей:

let specimen = searchController.active ? searchResults[indexPath.row] : specimens[indexPath.row]

Код выше проверяет, активен ли searchController, если да, то он извлекает образец из searchResults, если нет, то он вместо этого извлекает образец из specimens.

Наконец, вам нужно добавить функцию для сортировки возвращаемых результатов, когда пользователь нажимает кнопку в панели области действия.

Замените пустой scopeChanged(_:) следующим кодом:

@IBAction func scopeChanged(sender: AnyObject) {
 
  let scopeBar = sender as! UISegmentedControl
  let realm = try! Realm()
 
  switch scopeBar.selectedSegmentIndex {
  case 0:
    specimens = realm.objects(Specimen).sorted("name", ascending: true)
  case 1:
    specimens = realm.objects(Specimen).sorted("created", ascending: true)
  default:
    specimens = realm.objects(Specimen).sorted("name", ascending: true)
  }
  tableView.reloadData()
}

В коде выше вы проверяете, какая scope кнопка нажата - A-Z, или Date Added - и вызываете соответственно arraySortedByProperty(_:ascending:). По умолчанию список будет сортироваться по name.

Запустите приложение; задайте несколько различных поисков и посмотрите, что вы получите в результате!

Обновление записей

Вы разобрали добавление записей, но как их обновлять?

Если вы кликните в ячейке LogViewController, то перейдете непосредственно к AddNewEntryViewController, но поля будут пустыми. Конечно, первый шаг - это позволить пользователю редактировать поля для отображения существующих данных!

Откройте AddNewEntryViewController.swift и добавьте следующий вспомогательный метод классу:

func fillTextFields() {
  nameTextField.text = specimen.name
  categoryTextField.text = specimen.category.name
  descriptionTextField.text = specimen.specimenDescription
 
  selectedCategory = specimen.category
}

Этот метод в пользовательском интерфейсе заполнит данные образца. Помните, что AddNewEntryViewController до этого момента использовался только для новых образцов, так что эти поля всегда изначально были пустыми.

Добавьте следующие строки в конце viewDidLoad():

if let specimen = specimen {
  title = "Edit \(specimen.name)"
  fillTextFields()
} else {
  title = "Add New Specimen"
}

Приведенный выше код устанавливает заголовок панели навигации для того, чтобы сказать, добавляет ли пользователь новый экземпляр или обновляет уже существующий. Если это уже существующий образец, вы можете также вызвать вспомогательный метод для заполнения полей.

Теперь вам нужен способ для обновления записи образца с изменениями пользователя. Добавьте следующий метод классу:

func updateSpecimen() {
  let realm = try! Realm()
  try! realm.write {
    self.specimen.name = self.nameTextField.text!
    self.specimen.category = self.selectedCategory
    self.specimen.specimenDescription = self.descriptionTextField.text
  }
}

Как обычно, метод начинается с получения экземпляра Realm, а затем остальное оборачивается внутрь транзакции write(). Внутри транзакции, вы просто обновляете три поля данных.

Все, что вам нужно для обновления Specimen - это шесть строчек кода! :]

Теперь вам нужно вызвать описанный выше метод, когда пользователь нажимает Confirm. Найдите shouldPerformSegueWithIdentifier(_:sender:) и замените его следующим образом:

override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool {
  if validateFields() {
    if specimen != nil {
      updateSpecimen()
    } else {
      addNewSpecimen()
    }
    return true
  } else {
    return false
  }
}

Это вызовет вспомогательный метод для обновления данных в случае необходимости.

Теперь откройте LogViewController.swift и добавьте следующую реализацию для prepareForSegue(_:sender:):

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
  if (segue.identifier == "Edit") {
    let controller = segue.destinationViewController as! AddNewEntryController
    var selectedSpecimen: Specimen!
    let indexPath = tableView.indexPathForSelectedRow
 
    if searchController.active {
      let searchResultsController = searchController.searchResultsController as! UITableViewController
      let indexPathSearch = searchResultsController.tableView.indexPathForSelectedRow
      selectedSpecimen = searchResults[indexPathSearch!.row]
    } else {
      selectedSpecimen = specimens[indexPath!.row]
    }
    controller.specimen = selectedSpecimen
  }
}

Вам нужно передать выбранный образец экземпляру AddNewEntryController. Сложность произвести это с if else  заключается в том, что получение выбранного образца немного отличается, в зависимости от того, смотрит пользователь на результаты поиска или нет.

Запустите приложение, откройте окно Log и нажмите на существующий Specimen. Вы должны увидеть детали, заполненные поля, готовые к редактированию.

Конечный проект

Что дальше?

Дальше, вы можете продолжить изучать наши туториалы по мере их появления, а также, параллельно читать перевод официальной книги по языку программирования Swift. И, для более подробного изучения языка, вы можете пройти наши курсы!

Урок подготовил: Акулов Иван

Источник урока: Источник