數(shù)據(jù)持久化方案解析(六) —— 基于Realm的持久化存儲(二)

版本記錄

版本號 時間
V1.0 2018.12.21 星期五

前言

數(shù)據(jù)的持久化存儲是移動端不可避免的一個問題,很多時候的業(yè)務邏輯都需要我們進行本地化存儲解決和完成,我們可以采用很多持久化存儲方案,比如說plist文件(屬性列表)、preference(偏好設置)、NSKeyedArchiver(歸檔)、SQLite 3CoreData,這里基本上我們都用過。這幾種方案各有優(yōu)缺點,其中,CoreData是蘋果極力推薦我們使用的一種方式,我已經(jīng)將它分離出去一個專題進行說明講解。這個專題主要就是針對另外幾種數(shù)據(jù)持久化存儲方案而設立。
1. 數(shù)據(jù)持久化方案解析(一) —— 一個簡單的基于SQLite持久化方案示例(一)
2. 數(shù)據(jù)持久化方案解析(二) —— 一個簡單的基于SQLite持久化方案示例(二)
3. 數(shù)據(jù)持久化方案解析(三) —— 基于NSCoding的持久化存儲(一)
4. 數(shù)據(jù)持久化方案解析(四) —— 基于NSCoding的持久化存儲(二)
5. 數(shù)據(jù)持久化方案解析(五) —— 基于Realm的持久化存儲(一)

開始

首先看下寫作環(huán)境

Swift 4.2, iOS 12, Xcode 10

Realm是一個跨平臺的移動數(shù)據(jù)庫解決方案,專為移動應用程序而設計,可以與iOS項目集成。 與Core Data的封裝不同,Realm不依賴于Core Data甚至是SQLite后端。

本教程將向您介紹iOS上Realm的基本功能。 在本教程結(jié)束時,您將了解如何鏈接Realm框架,創(chuàng)建模型,執(zhí)行查詢和更新記錄。

這是一個場景:你已經(jīng)接受了國家公園管理局的實習生職位。 您的工作是記錄在美國最大的國家公園中發(fā)現(xiàn)的物種。

您需要助理來記錄您的發(fā)現(xiàn),但由于該機構(gòu)沒有預算來雇用新的,您決定為自己創(chuàng)建一個虛擬助手:一個名為Agents Partner的應用程序。

在Xcode中打開啟動項目。 目前,該應用程序僅包含使用MapKit的地圖功能,該功能已在項目中設置。

啟動項目缺少Realm,所以是時候添加它了。

注意:本教程是針對Realm 3.11.1編寫的。

安裝Realm的有效方法是使用CocoaPods

在starter項目的根目錄中,創(chuàng)建一個名為Podfile的新文件。 復制以下文本并將其粘貼到新創(chuàng)建的文件中:

platform :ios, '12.0'
use_frameworks!

target 'Agents Partner' do
  pod 'RealmSwift'
end

保存并關閉文件。

在終端和項目的根目錄中,運行以下命令:

pod install

這告訴CocoaPods掃描你的Podfile并安裝你在文件中列出的任何pod。 很簡約!

Realm可能需要安裝一會,所以請密切關注您的終端。 一旦完成,您將看到底部附近的一條線,Pod installation complete!

在Finder中,打開starter項目的根目錄。 請注意CocoaPods添加的文件夾以及Agents Partner.xcworkspace

如果您在Xcode中打開了起始項目,請立即關閉它并通過雙擊該文件打開.xcworkspace。 當您想要處理項目時,需要打開此文件。

您只需使用CocoaPods設置Realm。 構(gòu)建并運行項目以確保編譯所有內(nèi)容。 如果一切按預期進行,你會看到:


Concepts and Classes Overview - 概念和類概覽

為了更好地理解Realm的作用,下面是一些關于您將在本教程中使用的類的概念和信息:

  • Realm:Realm實例是框架的核心。它是您的基礎數(shù)據(jù)庫的訪問點,如Core Data managed object context。您可以使用Realm()初始值設定項創(chuàng)建實例。
  • Object:這是您的Realm模型。創(chuàng)建模型的行為定義了數(shù)據(jù)庫的schema。要創(chuàng)建模型,請將Object子類化并定義要作為屬性存儲的字段。
  • Relationships:通過聲明要引用的Object類型的屬性,可以在對象之間創(chuàng)建一對多關系。您可以通過List類型的屬性創(chuàng)建多對一和多對多關系。
  • Write Transactions:數(shù)據(jù)庫中的任何操作,如創(chuàng)建,編輯或刪除對象,都必須通過在Realm實例上調(diào)用write(_ :)來在寫入中執(zhí)行。
  • Queries:要從數(shù)據(jù)庫中檢索對象,請使用查詢。最簡單的查詢形式是在Realm實例上調(diào)用objects()并傳入您正在尋找的Object的類。如果您的數(shù)據(jù)檢索需求更加復雜,您可以使用謂詞,鏈接查詢并對結(jié)果進行排序。
  • ResultsResults是您從對象查詢返回的自動更新容器類型。它們與常規(guī)數(shù)組Arrays有許多相似之處,包括下標語法。

通過對Realm的簡要介紹,是時候讓你構(gòu)建項目的其余部分。


Your First Model

Models組打開Specimen.swift并添加以下實現(xiàn):

import Foundation
import RealmSwift

class Specimen: Object {
  @objc dynamic var name = ""
  @objc dynamic var specimenDescription = ""
  @objc dynamic var latitude = 0.0
  @objc dynamic var longitude = 0.0
  @objc dynamic var created = Date()
}

上面的代碼添加了一些屬性:

namesamplesDescription存儲樣本的名稱和描述。 Realm中的特定數(shù)據(jù)類型(如字符串)必須使用值初始化。 在這種情況下,您使用空字符串初始化它們。

latitudelongitude存儲樣本的坐標。 在這里,您將類型設置為Double并使用0.0初始化它們。

created存儲樣本的創(chuàng)建日期。 Date()返回當前日期,以便使用該值初始化該屬性。

在Realm中創(chuàng)建第一個模型后,您是否準備好在一個小挑戰(zhàn)中使用這些知識?

標本應分為不同的類別。 挑戰(zhàn)在于自己創(chuàng)建一個Category模型。 將文件命名為Category.swift,并為新模型提供名為name的單個String屬性。

如果您想檢查您的工作,解決方案如下:

Category.swift看起來類似如下

import Foundation
import RealmSwift

class Category: Object {
  @objc dynamic var name = "" 
}

您有一個Category模型,您需要以某種方式與Specimen模型相關聯(lián)。

回想一下上面的說明,聲明您可以通過聲明具有要鏈接的適當模型的屬性來創(chuàng)建模型之間的關系。

打開Specimen.swift并在其他屬性下面添加以下聲明:

@objc dynamic var category: Category!

這在SpecimenCategory之間建立了一對多的關系。 這意味著每個Specimen只能屬于一個Category,但每個Category可以有許多Specimens

您現(xiàn)在已經(jīng)擁有了基本數(shù)據(jù)模型。 是時候?qū)⒁恍┯涗浱砑拥侥臄?shù)據(jù)庫了!


Adding Records - 添加記錄

當用戶添加新樣本時,他們可以輸入樣本名稱并選擇一個類別。 打開CategoriesTableViewController.swift。 此視圖控制器在table view中顯示類別列表,以便用戶可以選擇一個。

在開始編寫代碼以集成Realm之前,您需要導入RealmSwift框架。 將以下行添加到文件的頂部,在import UIKit下面:

import RealmSwift

您將使用一些默認categories填充此table view。 這些Category實例可以存儲在Results的實例中。

CategoriesTableViewController現(xiàn)在有一個categories數(shù)組作為占位符。 在類定義的頂部找到以下代碼:

var categories: [Any] = []

用以下內(nèi)容替換該代碼:

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

如果要獲取對象,則始終可以定義所需的模型。 在上面的代碼中,首先創(chuàng)建一個Realm實例,然后通過調(diào)用objects(_:)來填充categories,并傳入所需模型類型的類名。

注意:為了簡化本教程中所需的代碼,您正在使用try!調(diào)用拋出錯誤的Realm方法時。 在您自己的代碼中,您應該使用trydo / catch來捕獲和處理錯誤。

您希望為用戶提供一些默認類別,以便在應用首次運行時進行選擇。

將以下輔助方法添加到類定義中:

private 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
        
        realm.add(newCategory)
      }
    }
    
    categories = realm.objects(Category.self) // 5
  }
}

以下是每個編號行的內(nèi)容:

  • 1) 如果count等于0,則表示數(shù)據(jù)庫沒有Category記錄。 這是第一次運行應用程序時的情況。
  • 2) 這將在realm上啟動事務,您現(xiàn)在可以將一些記錄添加到數(shù)據(jù)庫中。
  • 3) 在這里,您創(chuàng)建默認類別名稱列表,然后迭代它們。
  • 4) 對于每個類別名稱,您可以創(chuàng)建一個新的Category實例,填充name并將該對象添加到realm
  • 5) 您獲取您創(chuàng)建的所有類別并將其存儲在categories中。

將以下行添加到viewDidLoad()的末尾:

populateDefaultCategories()

這會調(diào)用輔助方法在視圖加載時填充測試類別。

現(xiàn)在您有了一些數(shù)據(jù),您將更新table view數(shù)據(jù)源方法以顯示類別。 查找tableView(_:cellForRowAt :)并在return cell之前添加以下內(nèi)容:

let category = categories[indexPath.row]
cell.textLabel?.text = category.name

此實現(xiàn)基于index pathcategories中檢索類別。 然后,它設置單元格的文本標簽以顯示類別的name

接下來,在您添加到CategoriesTableViewController的其他屬性下面添加此屬性:

var selectedCategory: Category!

您將使用此屬性存儲當前選定的Category

找到tableView(_:willSelectRowAtIndexPath :)并在return indexPath之前添加以下內(nèi)容:

selectedCategory = categories[indexPath.row]

這會將用戶的選擇存儲在您在上面聲明的屬性selectedCategory中。

構(gòu)建并運行您的應用程序。

將地圖縮放并平移到有趣的地方,并通過點擊右上角的+按鈕創(chuàng)建新的注釋。 點擊地圖圖釘將其選中,然后點擊注釋數(shù)據(jù)以編輯詳細信息。 最后,點擊Categorytext field以查看類別列表,如下所示:

您可以選擇一個類別,但只將其保存到屬性中,而不是保存在數(shù)據(jù)庫中的任何其他位置。 很高興看到應用程序中顯示了類別,但是在數(shù)據(jù)庫中查看記錄總是令人放心。 您可以通過Realm Browser執(zhí)行此操作。


Introducing the Realm Browser

Realm包含用于讀取和編輯數(shù)據(jù)庫的Realm Browser。 Realm數(shù)據(jù)庫格式是專有的,不是人類可讀的。

您可以在此處here下載Realm Browser


Working With Realm Browser

在開發(fā)應用程序時了解Realm數(shù)據(jù)庫的存儲位置非常重要 - 您可以使用一個巧妙的技巧來找出它的位置。

打開MapViewController.swift并將以下行添加到現(xiàn)有import語句下面的文件頂部:

import RealmSwift

在調(diào)用super.viewDidLoad()之后,將以下行添加到viewDidLoad()

print(Realm.Configuration.defaultConfiguration.fileURL!)

此行將數(shù)據(jù)庫位置打印到調(diào)試控制臺。 這是以后使用Realm Browser瀏覽數(shù)據(jù)庫的一步。

構(gòu)建并運行您的應用程序,您將看到它在Xcode控制臺中報告數(shù)據(jù)庫的位置。

轉(zhuǎn)到數(shù)據(jù)庫位置的最簡單方法是打開Finder,按Shift-Command-G并粘貼應用報告的路徑。

在Finder中打開文件夾后,您可能會看到一個或兩個文件。 其中一個是default.realm,它是您的數(shù)據(jù)庫文件。 第二個文件可能存在也可能不存在,是default.realm.lock。 鎖定文件可防止在使用數(shù)據(jù)庫時修改其他應用程序。

如果您尚未下載Realm Browser,請從App Store下載。 雙擊default.realm以使用Realm Browser打開它:

Realm Browser中打開數(shù)據(jù)庫后,您會看到Category旁邊有一個5。 這意味著該類包含五個記錄。 單擊一個類以檢查其中包含的各個字段。


Adding Categories

您現(xiàn)在可以設置邏輯來設置Specimencategory

打開AddNewEntryController.swift并在現(xiàn)有import語句下面導入RealmSwift framework

import RealmSwift

將以下屬性添加到類中:

var selectedCategory: Category!

您將使用它來存儲選定的Category

接下來,找到unwindFromCategories(segue :)并在其中添加以下實現(xiàn):

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

當用戶從您在上一步中設置的CategoriesTableViewController中選擇一個類別時,將調(diào)用unwindFromCategories(segue :)。 在這里,您檢索所選類別,將其存儲在selectedCategory中,并使用類別名稱填寫text field

您可以繼續(xù)創(chuàng)建您的第一個Specimen


Adding Specimens

仍然在AddNewEntryController.swift中,再向該類添加一個屬性:

var specimen: Specimen!

此屬性存儲新的specimen對象。

接下來,將此輔助方法添加到類中:

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

以下是上面代碼的作用:

  • 1) 首先,像以前一樣獲取Realm實例。
  • 2) 啟動寫入事務以添加新的Specimen
  • 3) 創(chuàng)建一個新的Specimen實例。
  • 4) 分配Specimen值。 值來自用戶界面中的輸入文本字段,選定的類別和地圖注釋中的坐標。
  • 5) 將新Specimen添加到領域。
  • 6) 將新Specimen分配給Specimen屬性。

您需要某種驗證器來確保所有字段都填充在您的Specimen中。 存在AddNewEntryViewController中的validateFields()以檢查Specimen名稱和描述。 由于您已添加了為樣本分配類別的功能,因此您也將檢查該字段。

validateFields()中找到如下所示的行:

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

修改這一行,如下所示:

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

這將驗證是否已填充所有字段以及您是否已選擇類別。

接下來,將以下方法添加到類中:

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

在上面的代碼中,您調(diào)用方法來驗證字段。 如果所有內(nèi)容都已填寫,則添加新樣本并返回true;否則,你返回false

建立并運行。 點擊+按鈕創(chuàng)建一個新樣本。 填寫名稱和說明,選擇一個類別,然后點擊Confirm將您的Specimen添加到數(shù)據(jù)庫。

視圖控制器dismiss,但似乎沒有任何事情發(fā)生。 這是怎么回事?

您已將記錄發(fā)布到您的realm,但您尚未使用新樣本填充地圖。


Retrieving Records

您已將樣本添加到要在地圖上顯示的數(shù)據(jù)庫中。

首先看一下Realm Browser中更新的數(shù)據(jù)庫:

您將看到填充了其字段的單個樣本,以及MKAnnotation中的緯度和經(jīng)度。 您還可以看到標本類別的鏈接;這意味著您的一對多Category關系正在按預期工作。

單擊Specimen記錄中的Category以查看Category記錄本身。

接下來,您將在應用程序中填充地圖。

打開SpecimenAnnotation.swift并向該類添加屬性:

var specimen: Specimen?

這保存了注釋的Specimen

接下來,使用以下內(nèi)容替換初始化程序:

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

這里的更改是添加一個傳遞Specimen的選項。 樣本的默認值為nil,這意味著如果您愿意,可以省略該參數(shù)。 如果沒有標本,應用程序的其余部分仍然可以使用前三個參數(shù)調(diào)用初始化程序。

打開MapViewController.swift并向該類添加一個新屬性:

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

由于您希望在此屬性中存儲標本集合,因此請向Realm實例詢問Specimen類型的所有對象。

現(xiàn)在,將以下方法添加到類中:

func populateMap() {
  mapView.removeAnnotations(mapView.annotations) // 1

  specimens = try! Realm().objects(Specimen.self) // 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) 清除地圖上的所有現(xiàn)有注釋以重新開始。
  • 2) 刷新您的specimens屬性。
  • 3) 遍歷specimens并使用樣本的坐標以及其namecategory創(chuàng)建SpecimenAnnotation
  • 4) 將每個samplesAnnotation添加到MKMapView

你需要從某個地方調(diào)用這個方法。 找到viewDidLoad()并將此行添加到其實現(xiàn)的末尾:

populateMap()

這可確保地圖在視圖控制器加載時顯示樣本。

現(xiàn)在,您將更改注釋以包含樣本名稱和類別。 找到unwindFromAddNewEntry(segue :)并用以下實現(xiàn)替換該方法:

@IBAction func unwindFromAddNewEntry(segue: UIStoryboardSegue) {
  let addNewEntryController = segue.source as! AddNewEntryViewController
  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返回并且有一個新的樣本要添加到地圖中,系統(tǒng)將調(diào)用此方法。 將新樣本添加到地圖時,會獲得通用注釋圖標。 對于您的類別,您希望將該圖標更改為特定于類別的圖標。

在這里,您刪除添加到地圖的最后一個注釋,并將其替換為顯示樣本名稱和類別的注釋。

建立并運行。 創(chuàng)建一些不同類別的新標本,并查看地圖如何更新:

1. A Different View

您可能已經(jīng)注意到地圖視圖左上角的Log按鈕。 除了地圖之外,該應用程序還有一個基于文本的table view,列出了所有注釋,稱為Log View。 接下來,您將使用一些數(shù)據(jù)填充此表視圖。

打開LogViewController.swift并導入RealmSwift

import RealmSwift

然后,用以下內(nèi)容替換specimens屬性:

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

在上面的代碼中,您將占位符數(shù)組替換為包含SpecimensResults,就像在MapViewController中一樣。 它們將按name排序。

接下來,在return cell之前將以下內(nèi)容添加到tableView(_:cellForRowAt :)

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")
}

此方法使用樣本的namecategory填充單元格。

構(gòu)建并運行您的應用程序。 點擊Log,您將在table view中看到所有輸入的樣本,如下所示:

2. Fetches With Predicates

您希望自己的應用有一個方便的搜索功能。 您的starter項目包含一個UISearchController實例;您將添加一些特定于您的應用的修改,以使其與Realm一起使用。

LogViewController.swift中,將searchResults屬性替換為以下內(nèi)容:

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

向類中添加這個方法

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.self)
      .filter(predicate).sorted(byKeyPath: "name", ascending: true) // 3
  case 1:
    searchResults = realm.objects(Specimen.self).filter(predicate)
      .sorted(byKeyPath: "created", ascending: true) // 4
  default:
    searchResults = realm.objects(Specimen.self).filter(predicate) // 5
  }
}

以下是上述函數(shù)的作用:

  • 1) 首先,創(chuàng)建一個謂詞,用于搜索以searchString開頭的nameBEGINSWITH之后的[c]表示不區(qū)分大小寫的搜索。
  • 2) 然后,從搜索欄中獲取對當前所選范圍索引的引用。
  • 3) 如果選擇了第一個分段按鈕,則按名稱升序?qū)Y(jié)果進行排序。
  • 4) 如果選擇了第二個按鈕,則按創(chuàng)建日期升序?qū)Y(jié)果進行排序。
  • 5) 如果未選擇任何按鈕,請不要對結(jié)果進行排序,按照從數(shù)據(jù)庫返回的順序進行排序。

現(xiàn)在,當用戶與search field交互時,您實際上將執(zhí)行過濾。 在updateSearchResults(for :)中,在方法的開頭添加以下兩行:

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

由于搜索結(jié)果table view調(diào)用相同的數(shù)據(jù)源方法,因此您需要對tableView(_:cellForRowAt :)進行一些小的更改,以處理主日志表視圖和搜索結(jié)果。 在該方法中,找到分配給specimen的那一行:

let specimen = specimens[indexPath.row]

刪除它并用以下內(nèi)容替換它:

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

此代碼檢查searchController是否處于活動狀態(tài)。 如果是這樣,它將從searchResults中檢索樣本。 如果沒有,它會從specimens中取回樣本。

接下來,當用戶點擊范圍欄中的按鈕時,您將添加一個函數(shù)來對返回的結(jié)果進行排序。

將以下實現(xiàn)添加到scopeChanged(sender :)

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

在這里,您可以檢查按下的范圍按鈕(A-ZDate Added)并進行排序。 默認情況下,列表將按name排序。

構(gòu)建并運行您的應用程序。 嘗試一些不同的搜索,看看你得到的結(jié)果。


Updating Records

您已經(jīng)介紹了添加記錄,但是當您想要更新記錄時呢?

如果您點擊LogViewController中的單元格,您將轉(zhuǎn)到AddNewEntryViewController,但字段為空。 讓用戶編輯字段的第一步是顯示現(xiàn)有數(shù)據(jù)。

打開AddNewEntryViewController.swift并將以下輔助方法添加到類中:

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

  selectedCategory = specimen.category
}

該方法用樣本數(shù)據(jù)填充用戶界面。 請記住,到目前為止,AddNewEntryViewController僅用于新標本,因此這些字段始終為空。

接下來,將以下行添加到viewDidLoad()的末尾:

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

此代碼設置導航標題以指示用戶是否正在添加或更新樣本。 如果它是現(xiàn)有樣本,您還可以調(diào)用輔助方法來填充字段。

您需要一種方法來更新樣本記錄和用戶的更改。 添加以下方法:

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

像往常一樣,該方法從獲取Realm實例開始,然后其余部分封裝在write()事務中。 在事務內(nèi)部,您更新數(shù)據(jù)字段。

只需要六行代碼來更新Specimen記錄!

接下來,當用戶點擊Confirm時,您將調(diào)用上述方法。 找到shouldPerformSegue(withIdentifier:sender :)并將addNewSpecimen()的調(diào)用替換為以下內(nèi)容:

if specimen != nil {
  updateSpecimen()
} else {
  addNewSpecimen()
}

這會調(diào)用您的方法在適當時更新數(shù)據(jù)。

打開LogViewController.swift并為prepare(for:sender :)添加以下實現(xiàn):

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  if (segue.identifier == "Edit") {
    let controller = segue.destination as! AddNewEntryViewController
    var selectedSpecimen: Specimen!
    let indexPath = tableView.indexPathForSelectedRow
      
    if searchController.isActive {
      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的復雜性是因為取決于用戶是否正在查看搜索結(jié)果而使所選樣本不同。

構(gòu)建并運行您的應用程序。 打開日志視圖,然后點擊Specimen。 您將看到填充了所有字段并準備編輯的詳細信息。

后記

本篇主要講述了基于Realm的持久化存儲,感興趣的給個贊或者關注~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。