美文网首页
数据持久化方案解析(七) —— 基于Realm的持久化存储(三)

数据持久化方案解析(七) —— 基于Realm的持久化存储(三)

作者: 刀客传奇 | 来源:发表于2018-12-21 18:44 被阅读52次

    版本记录

    版本号 时间
    V1.0 2018.12.21 星期五

    前言

    数据的持久化存储是移动端不可避免的一个问题,很多时候的业务逻辑都需要我们进行本地化存储解决和完成,我们可以采用很多持久化存储方案,比如说plist文件(属性列表)、preference(偏好设置)、NSKeyedArchiver(归档)、SQLite 3CoreData,这里基本上我们都用过。这几种方案各有优缺点,其中,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的持久化存储,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

          本文标题:数据持久化方案解析(七) —— 基于Realm的持久化存储(三)

          本文链接:https://www.haomeiwen.com/subject/hysvkqtx.html