版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.12.21 星期五 |
前言
数据的持久化存储是移动端不可避免的一个问题,很多时候的业务逻辑都需要我们进行本地化存储解决和完成,我们可以采用很多持久化存储方案,比如说
plist
文件(属性列表)、preference
(偏好设置)、NSKeyedArchiver
(归档)、SQLite 3
、CoreData
,这里基本上我们都用过。这几种方案各有优缺点,其中,CoreData是苹果极力推荐我们使用的一种方式,我已经将它分离出去一个专题进行说明讲解。这个专题主要就是针对另外几种数据持久化存储方案而设立。
1. 数据持久化方案解析(一) —— 一个简单的基于SQLite持久化方案示例(一)
2. 数据持久化方案解析(二) —— 一个简单的基于SQLite持久化方案示例(二)
3. 数据持久化方案解析(三) —— 基于NSCoding的持久化存储(一)
4. 数据持久化方案解析(四) —— 基于NSCoding的持久化存储(二)
5. 数据持久化方案解析(五) —— 基于Realm的持久化存储(一)
6. 数据持久化方案解析(六) —— 基于Realm的持久化存储(二)
源码
1. Swift
首先看一下工程结构
然后,我们看一下sb中的内容
下面就是源码了
1. AppDelegate.swift
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let rayGreen = UIColor(named: "RayGreen")
UITextField.appearance().tintColor = rayGreen
UITextView.appearance().tintColor = rayGreen
return true
}
}
2. LogCell.swift
import UIKit
class LogCell: UITableViewCell {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var subtitleLabel: UILabel!
@IBOutlet weak var distanceLabel: UILabel!
@IBOutlet weak var iconImageView: UIImageView!
}
3. Specimen.swift
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()
@objc dynamic var category: Category!
}
4. SpecimenAnnotation.swift
import UIKit
import MapKit
class SpecimenAnnotation: NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var specimen: Specimen?
var subtitle: String?
var title: String?
init(coordinate: CLLocationCoordinate2D, title: String, subtitle: String, specimen: Specimen? = nil) {
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
self.specimen = specimen
}
}
5. Category.swift
import Foundation
import RealmSwift
class Category: Object {
@objc dynamic var name = ""
}
6. AddNewEntryViewController.swift
import RealmSwift
import UIKit
//
// MARK: - Add New Entry View Controller
//
class AddNewEntryViewController: UIViewController {
@IBOutlet weak var categoryTextField: UITextField!
@IBOutlet weak var descriptionTextField: UITextView!
@IBOutlet weak var nameTextField: UITextField!
//
// MARK: - Variables And Properties
//
var selectedAnnotation: SpecimenAnnotation!
var selectedCategory: Category!
var specimen: Specimen!
//
// MARK: - IBActions
//
@IBAction func unwindFromCategories(segue: UIStoryboardSegue) {
if segue.identifier == "CategorySelectedSegue" {
let categoriesController = segue.source as! CategoriesTableViewController
selectedCategory = categoriesController.selectedCategory
categoryTextField.text = selectedCategory.name
}
}
//
// MARK: - Private Methods
//
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
}
}
func fillTextFields() {
nameTextField.text = specimen.name
categoryTextField.text = specimen.category.name
descriptionTextField.text = specimen.specimenDescription
selectedCategory = specimen.category
}
func updateSpecimen() {
let realm = try! Realm()
try! realm.write {
specimen.name = nameTextField.text!
specimen.category = selectedCategory
specimen.specimenDescription = descriptionTextField.text
}
}
func validateFields() -> Bool {
if nameTextField.text!.isEmpty || descriptionTextField.text!.isEmpty || selectedCategory == nil {
let alertController = UIAlertController(title: "Validation Error",
message: "All fields must be filled",
preferredStyle: .alert)
let alertAction = UIAlertAction(title: "OK", style: .destructive) { alert in
alertController.dismiss(animated: true, completion: nil)
}
alertController.addAction(alertAction)
present(alertController, animated: true, completion: nil)
return false
} else {
return true
}
}
//
// MARK: - View Controller
//
override func shouldPerformSegue(withIdentifier identifier: String,
sender: Any?) -> Bool {
if validateFields() {
if specimen != nil {
updateSpecimen()
} else {
addNewSpecimen()
}
return true
} else {
return false
}
}
override func viewDidLoad() {
super.viewDidLoad()
if let specimen = specimen {
title = "Edit \(specimen.name)"
fillTextFields()
} else {
title = "Add New Specimen"
}
}
}
//
// MARK: - Text Field Delegate
//
extension AddNewEntryViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
performSegue(withIdentifier: "Categories", sender: self)
}
}
7. CategoriesTableViewController.swift
import RealmSwift
import UIKit
//
// MARK: - Categories Table View Controller
//
class CategoriesTableViewController: UITableViewController {
//
// MARK: - Variables And Properties
//
let realm = try! Realm()
lazy var categories: Results<Category> = { self.realm.objects(Category.self) }()
var selectedCategory: Category!
//
// MARK: - Private Methods
//
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
}
}
//
// MARK: - View Controller
//
override func viewDidLoad() {
super.viewDidLoad()
populateDefaultCategories()
}
}
//
// MARK: - Table View Data Source
//
extension CategoriesTableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell", for: indexPath)
let category = categories[indexPath.row]
cell.textLabel?.text = category.name
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categories.count
}
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
selectedCategory = categories[indexPath.row]
return indexPath
}
}
8. LogViewController.swift
import MapKit
import RealmSwift
import UIKit
//
// MARK: - Log View Controller
//
class LogViewController: UITableViewController {
//
// MARK: - IBOutlets
//
@IBOutlet weak var segmentedControl: UISegmentedControl!
//
// MARK: - Variables And Properties
//
var searchResults = try! Realm().objects(Specimen.self)
var searchController: UISearchController!
var specimens = try! Realm().objects(Specimen.self).sorted(byKeyPath: "name", ascending: true)
//
// MARK: - IBActions
//
@IBAction func scopeChanged(sender: Any) {
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()
}
//
// MARK: - Private Methods
//
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
}
}
//
// MARK: - View Controller
//
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
}
}
override func viewDidLoad() {
super.viewDidLoad()
let searchResultsController = UITableViewController(style: .plain)
searchResultsController.tableView.delegate = self
searchResultsController.tableView.dataSource = self
searchResultsController.tableView.rowHeight = 63
searchResultsController.tableView.register(LogCell.self, forCellReuseIdentifier: "LogCell")
searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = self
searchController.searchBar.sizeToFit()
searchController.searchBar.tintColor = .white
searchController.searchBar.delegate = self
searchController.searchBar.barTintColor = UIColor(named: "RayGreen")
tableView.tableHeaderView?.addSubview(searchController.searchBar)
definesPresentationContext = true
}
}
//
// MARK: - Search Bar Delegate
//
extension LogViewController: UISearchBarDelegate {
}
//
// MARK: - Search Results Updatings
//
extension LogViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
let searchString = searchController.searchBar.text!
filterResultsWithSearchString(searchString: searchString)
let searchResultsController = searchController.searchResultsController as! UITableViewController
searchResultsController.tableView.reloadData()
}
}
//
// MARK: - Table View Data Source
extension LogViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "LogCell") as! LogCell
let specimen = searchController.isActive ? searchResults[indexPath.row] : 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
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchController.isActive ? searchResults.count : specimens.count
}
}
9. MapViewController.swift
import CoreLocation
import MapKit
import RealmSwift
import UIKit
//
// MARK: - Map View Controller
//
class MapViewController: UIViewController {
//
// MARK: - IBOutlets
//
@IBOutlet weak var mapView: MKMapView!
//
// MARK: - Constants
//
let kDistanceMeters: CLLocationDistance = 500
//
// MARK: - Variables And Properties
//
var lastAnnotation: MKAnnotation!
var locationManager = CLLocationManager()
var specimens = try! Realm().objects(Specimen.self)
var userLocated = false
//
// MARK: - IBActions
//
@IBAction func addNewEntryTapped() {
addNewPin()
}
@IBAction func centerToUserLocationTapped() {
centerToUsersLocation()
}
@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;
}
//
// MARK: - Private Methods
//
func addNewPin() {
if lastAnnotation != nil {
let alertController = UIAlertController(title: "Annotation already dropped",
message: "There is an annotation on screen. Try dragging it if you want to change its location!",
preferredStyle: .alert)
let alertAction = UIAlertAction(title: "OK", style: .destructive) { alert in
alertController.dismiss(animated: true, completion: nil)
}
alertController.addAction(alertAction)
present(alertController, animated: true, completion: nil)
} else {
let specimen = SpecimenAnnotation(coordinate: mapView.centerCoordinate, title: "Empty", subtitle: "Uncategorized")
mapView.addAnnotation(specimen)
lastAnnotation = specimen
}
}
func centerToUsersLocation() {
let center = mapView.userLocation.coordinate
let zoomRegion: MKCoordinateRegion = MKCoordinateRegion(center: center, latitudinalMeters: kDistanceMeters, longitudinalMeters: kDistanceMeters)
mapView.setRegion(zoomRegion, animated: true)
}
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
}
}
//
// MARK: - View Controller
//
override func viewDidLoad() {
super.viewDidLoad()
print(Realm.Configuration.defaultConfiguration.fileURL!)
title = "Map"
locationManager.delegate = self
if CLLocationManager.authorizationStatus() == .notDetermined {
locationManager.requestWhenInUseAuthorization()
} else {
locationManager.startUpdatingLocation()
}
populateMap()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "NewEntry") {
let controller = segue.destination as! AddNewEntryViewController
let specimenAnnotation = sender as! SpecimenAnnotation
controller.selectedAnnotation = specimenAnnotation
}
}
}
//MARK: - LocationManager Delegate
extension MapViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
status != .notDetermined ? mapView.showsUserLocation = true : print("Authorization to use location data denied")
}
}
//MARK: - Map View Delegate
extension MapViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, annotationView: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if let specimenAnnotation = annotationView.annotation as? SpecimenAnnotation {
performSegue(withIdentifier: "NewEntry", sender: specimenAnnotation)
}
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView,
didChange newState: MKAnnotationView.DragState, fromOldState oldState: MKAnnotationView.DragState) {
if newState == .ending {
view.dragState = .none
}
}
func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
for annotationView in views {
if (annotationView.annotation is SpecimenAnnotation) {
annotationView.transform = CGAffineTransform(translationX: 0, y: -500)
UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveLinear, animations: {
annotationView.transform = CGAffineTransform(translationX: 0, y: 0)
}, completion: nil)
}
}
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard let subtitle = annotation.subtitle! else {
return nil
}
if (annotation is SpecimenAnnotation) {
if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: subtitle) {
return annotationView
} else {
let currentAnnotation = annotation as! SpecimenAnnotation
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: subtitle)
switch subtitle {
case "Uncategorized":
annotationView.image = UIImage(named: "IconUncategorized")
case "Arachnids":
annotationView.image = UIImage(named: "IconArachnid")
case "Birds":
annotationView.image = UIImage(named: "IconBird")
case "Mammals":
annotationView.image = UIImage(named: "IconMammal")
case "Flora":
annotationView.image = UIImage(named: "IconFlora")
case "Reptiles":
annotationView.image = UIImage(named: "IconReptile")
default:
annotationView.image = UIImage(named: "IconUncategorized")
}
annotationView.isEnabled = true
annotationView.canShowCallout = true
let detailDisclosure = UIButton(type: .detailDisclosure)
annotationView.rightCalloutAccessoryView = detailDisclosure
if currentAnnotation.title == "Empty" {
annotationView.isDraggable = true
}
return annotationView
}
}
return nil
}
}
后记
本篇主要讲述了基于Realm的持久化存储,感兴趣的给个赞或者关注~~~
网友评论