iOS学习- 24 Core Data by Tutorials - CH04

1.) Bubble tea finder APP


data model:



View Controller:


JSON Data File:


{
  "meta": {
    "code": 200
  },
  "response": {
    "venues": [
               {
               "id": "4e3c89c8aeb73139a16d84ee",
               "name": "Kung Fu Tea 功夫茶",
               "contact": {
               "phone": "2123343536",
               "formattedPhone": "(212) 334-3536"
               },
               "location": {
               "address": "234 Canal St",
               "crossStreet": "at Centre St.",
               "lat": 40.717854,
               "lng": -74.000208,
               "distance": 4787,
               "postalCode": "10013",
               "cc": "US",
               "city": "New York",
               "state": "NY",
               "country": "United States",
               "formattedAddress": [
                                    "234 Canal St (at Centre St.)",
                                    "New York, NY 10013",
                                    "United States"
                                    ]
               },
               "categories": [
                              {
                              "id": "52e81612bcbc57f1066b7a0c",
                              "name": "Bubble Tea Shop",
                              "pluralName": "Bubble Tea Shops",
                              "shortName": "Bubble Tea",
                              "icon": {
                              "prefix": "https://ss1.4sqi.net/img/categories_v2/food/bubble_",
                              "suffix": ".png"
                              },
                              "primary": true
                              }
                              ],
               "verified": true,
               "stats": {
               "checkinsCount": 6820,
               "usersCount": 3530,
               "tipCount": 60
               },
               "price":{
               "tier":1,
               "message":"Cheap",
               "currency":"$"
               },
               "menu": {
               "type": "Menu",
               "label": "Menu",
               "anchor": "View Menu",
               "url": "https://foursquare.com/v/kung-fu-tea-%E5%8A%9F%E5%A4%AB%E8%8C%B6/4e3c89c8aeb73139a16d84ee/menu",
               "mobileUrl": "https://foursquare.com/v/4e3c89c8aeb73139a16d84ee/device_menu"
               },
               "specials": {
               "count": 0,
               "items": []
               },
               "hereNow": {
               "count": 0,
               "summary": "0 people here",
               "groups": []
               },
               "referralId": "v-1408917566"
               },
               {
               "id": "535aabd5498e00b596216d81",
               "name": "Vivi Bubble Tea",

...




CoreDataStatck.swift


/*
 * Copyright (c) 2016 Razeware LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */


import CoreData


class CoreDataStack {
  
  let modelName = "Bubble_Tea_Finder"
  
  lazy var context: NSManagedObjectContext = {
    
    var managedObjectContext = NSManagedObjectContext(
      concurrencyType: .MainQueueConcurrencyType)
    
    managedObjectContext.persistentStoreCoordinator = self.psc
    return managedObjectContext
    }()
  
  private lazy var psc: NSPersistentStoreCoordinator = {
    
    let coordinator = NSPersistentStoreCoordinator(
      managedObjectModel: self.managedObjectModel)
    
    let url = self.applicationDocumentsDirectory
      .URLByAppendingPathComponent(self.modelName)
    
    do {
      let options =
      [NSMigratePersistentStoresAutomaticallyOption : true]
      
      try coordinator.addPersistentStoreWithType(
        NSSQLiteStoreType, configuration: nil, URL: url,
        options: options)
    } catch  {
      print("Error adding persistent store.")
    }
    
    return coordinator
    }()
  
  private lazy var managedObjectModel: NSManagedObjectModel = {
    
    let modelURL = NSBundle.mainBundle()
      .URLForResource(self.modelName,
        withExtension: "momd")!
    return NSManagedObjectModel(contentsOfURL: modelURL)!
    }()
  
  private lazy var applicationDocumentsDirectory: NSURL = {
    let urls = NSFileManager.defaultManager().URLsForDirectory(
      .DocumentDirectory, inDomains: .UserDomainMask)
    return urls[urls.count-1]
    }()
  
  func saveContext () {
    if context.hasChanges {
      do {
        try context.save()
      } catch let error as NSError {
        print("Error: \(error.localizedDescription)")
        abort()
      }
    }
  }
}


AppDelegate.swift


/*
 * Copyright (c) 2016 Razeware LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
import UIKit
import CoreData


@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  
  var window: UIWindow?
  lazy var  coreDataStack = CoreDataStack()
  
  
  func application(application: UIApplication,
    didFinishLaunchingWithOptions
    launchOptions: [NSObject: AnyObject]?) -> Bool {
      
      importJSONSeedDataIfNeeded()
      
      let navController = window!.rootViewController as! UINavigationController
      let viewController = navController.topViewController as! ViewController
      viewController.coreDataStack = coreDataStack
      
      return true
  }
  
  func applicationWillTerminate(application: UIApplication) {
    coreDataStack.saveContext()
  }
  
  func importJSONSeedDataIfNeeded() {
    
    let fetchRequest = NSFetchRequest(entityName: "Venue")
    var error: NSError? = nil
    
    let results =
    coreDataStack.context.countForFetchRequest(fetchRequest,
      error: &error)
    
    if (results == 0) {
      
      do {
        let results =
        try coreDataStack.context.executeFetchRequest(fetchRequest) as! [Venue]
        
        for object in results {
          let team = object as Venue
          coreDataStack.context.deleteObject(team)
        }
        
        coreDataStack.saveContext()
        importJSONSeedData()
        
      } catch let error as NSError {
        print("Error fetching: \(error.localizedDescription)")
      }
    }
  }
  
  func importJSONSeedData() {
    let jsonURL = NSBundle.mainBundle().URLForResource("seed", withExtension: "json")
    let jsonData = NSData(contentsOfURL: jsonURL!)


    let venueEntity = NSEntityDescription.entityForName("Venue", inManagedObjectContext: coreDataStack.context)
    let locationEntity = NSEntityDescription.entityForName("Location", inManagedObjectContext: coreDataStack.context)
    let categoryEntity = NSEntityDescription.entityForName("Category", inManagedObjectContext: coreDataStack.context)
    let priceEntity = NSEntityDescription.entityForName("PriceInfo", inManagedObjectContext: coreDataStack.context)
    let statsEntity = NSEntityDescription.entityForName("Stats", inManagedObjectContext: coreDataStack.context)
    
    do {
      let jsonDict = try NSJSONSerialization.JSONObjectWithData(jsonData!, options: .AllowFragments) as! NSDictionary
      let jsonArray = jsonDict.valueForKeyPath("response.venues") as! NSArray
      
      for jsonDictionary in jsonArray {
        
        let venueName = jsonDictionary["name"] as? String
        let venuePhone = jsonDictionary.valueForKeyPath("contact.phone") as? String
        let specialCount = jsonDictionary.valueForKeyPath("specials.count") as? NSNumber
        
        let locationDict = jsonDictionary["location"] as! NSDictionary
        let priceDict = jsonDictionary["price"] as! NSDictionary
        let statsDict = jsonDictionary["stats"] as! NSDictionary
        
        let location = Location(entity: locationEntity!, insertIntoManagedObjectContext: coreDataStack.context)
        location.address = locationDict["address"] as? String
        location.city = locationDict["city"] as? String
        location.state = locationDict["state"] as? String
        location.zipcode = locationDict["postalCode"] as? String
        location.distance = locationDict["distance"] as? NSNumber
        
        let category = Category(entity: categoryEntity!, insertIntoManagedObjectContext: coreDataStack.context)
        
        let priceInfo = PriceInfo(entity: priceEntity!, insertIntoManagedObjectContext: coreDataStack.context)
        priceInfo.priceCategory = priceDict["currency"] as? String
        
        let stats = Stats(entity: statsEntity!, insertIntoManagedObjectContext: coreDataStack.context)
        stats.checkinsCount = statsDict["checkinsCount"] as? NSNumber
        stats.usersCount = statsDict["userCount"] as? NSNumber
        stats.tipCount = statsDict["tipCount"] as? NSNumber
        
        let venue = Venue(entity: venueEntity!, insertIntoManagedObjectContext: coreDataStack.context)
        venue.name = venueName
        venue.phone = venuePhone
        venue.specialCount = specialCount
        venue.location = location
        venue.category = category
        venue.priceInfo = priceInfo
        venue.stats = stats
      }
      
      coreDataStack.saveContext()
    } catch let error as NSError {
      print("Deserialization error: \(error.localizedDescription)")
    }
  }

  
}




ViewController.swift


/*
 * Copyright (c) 2016 Razeware LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */


import UIKit
import CoreData




let filterViewControllerSegueIdentifier = "toFilterViewController"
let venueCellIdentifier = "VenueCell"


class ViewController: UIViewController {
  
  @IBOutlet weak var tableView: UITableView!
  var coreDataStack: CoreDataStack!
    var fetchRequest: NSFetchRequest!
    var venues: [Venue]! = []
    var asyncFetchRequest: NSAsynchronousFetchRequest!
    
  override func viewDidLoad() {
    super.viewDidLoad()
    
    let batchUpdate = NSBatchUpdateRequest(entityName: "Venue")
    
    batchUpdate.propertiesToUpdate = ["favorite": NSNumber(bool: true)]
    batchUpdate.affectedStores = coreDataStack.context.persistentStoreCoordinator!.persistentStores
    batchUpdate.resultType = .UpdatedObjectsCountResultType
    
    do {
        let batchResult = try coreDataStack.context.executeRequest(batchUpdate) as! NSBatchUpdateResult
        print("Records updated \(batchResult.result!)")
    } catch let error as NSError {
        print("Could not update \(error), \(error.userInfo)")
    }
    
    
    //let model = coreDataStack.context.persistentStoreCoordinator!.managedObjectModel
    
    //fetchRequest = model.fetchRequestTemplateForName("FetchRequest")
    
    //1
    fetchRequest = NSFetchRequest(entityName: "Venue")
    
    //2
    asyncFetchRequest =
        NSAsynchronousFetchRequest(fetchRequest: fetchRequest)
        { [unowned self] (result: NSAsynchronousFetchResult! )
            -> Void in
            self.venues = result.finalResult as! [Venue]
            self.tableView.reloadData()
    }
    
    //3
    do {
        try coreDataStack.context.executeRequest(asyncFetchRequest)
    } catch let error as NSError {
        print("Could not fetch \(error), \(error.userInfo)")
    }
    
    //fetchAndReload()


  }


    func fetchAndReload() {
        do {
            venues = try coreDataStack.context.executeFetchRequest(fetchRequest) as! [Venue]
            tableView.reloadData()
        } catch let error as NSError {
            print("Could not fetch \(error), \(error.userInfo)")
        }
    }
    
  override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    
    if segue.identifier == filterViewControllerSegueIdentifier {
        let navController = segue.destinationViewController as! UINavigationController
        
        let filterVC = navController.topViewController as! FilterViewController
        filterVC.coreDataStack = coreDataStack
        
        filterVC.delegate = self
    
    }
  }
  
  @IBAction func unwindToVenuListViewController(segue: UIStoryboardSegue) {
    
  }
}






extension ViewController: UITableViewDataSource {
  
  func tableView(tableView: UITableView,
    numberOfRowsInSection section: Int) -> Int {
    //return 10
    return venues.count
  }


  
  func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier(venueCellIdentifier)!
    //cell.textLabel!.text = "Bubble Tea Venue"
    //cell.detailTextLabel!.text = "Price Info"
    let venue = venues[indexPath.row]
    cell.textLabel!.text = venue.name
    cell.detailTextLabel!.text = venue.priceInfo?.priceCategory
    return cell
  }
}


extension ViewController: FilterViewControllerDelegate {
    func filterViewController(filter: FilterViewController, didSelectPredicate predicate: NSPredicate?, sortDescriptor: NSSortDescriptor?) {
        fetchRequest.predicate = nil
        fetchRequest.sortDescriptors = nil
        
        if let fetchPredicate = predicate {
            fetchRequest.predicate = fetchPredicate
        }
        
        if let sr = sortDescriptor {
            fetchRequest.sortDescriptors = [sr]
        }
        
        fetchAndReload()
    }
}


FilterViewController.swift

/*
 * Copyright (c) 2016 Razeware LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */


import UIKit
import CoreData


protocol FilterViewControllerDelegate: class {
    func filterViewController(filter: FilterViewController, didSelectPredicate predicate:NSPredicate?, sortDescriptor:NSSortDescriptor?)
}


class FilterViewController: UITableViewController {
  
  @IBOutlet weak var firstPriceCategoryLabel: UILabel!
  @IBOutlet weak var secondPriceCategoryLabel: UILabel!
  @IBOutlet weak var thirdPriceCategoryLabel: UILabel!
  @IBOutlet weak var numDealsLabel: UILabel!
  
  //Price section
  @IBOutlet weak var cheapVenueCell: UITableViewCell!
  @IBOutlet weak var moderateVenueCell: UITableViewCell!
  @IBOutlet weak var expensiveVenueCell: UITableViewCell!
  
  //Most popular section
  @IBOutlet weak var offeringDealCell: UITableViewCell!
  @IBOutlet weak var walkingDistanceCell: UITableViewCell!
  @IBOutlet weak var userTipsCell: UITableViewCell!
  
  //Sort section
  @IBOutlet weak var nameAZSortCell: UITableViewCell!
  @IBOutlet weak var nameZASortCell: UITableViewCell!
  @IBOutlet weak var distanceSortCell: UITableViewCell!
  @IBOutlet weak var priceSortCell: UITableViewCell!


    var coreDataStack: CoreDataStack!
    
    weak var delegate: FilterViewControllerDelegate?
    var selectedSortDescriptor: NSSortDescriptor?
    var selectedPredicate: NSPredicate?
    
    lazy var nameSortDescriptor: NSSortDescriptor = {
        var sd = NSSortDescriptor(key: "name", ascending: true, selector: "localizedStandardCompare:")
        return sd
    }()


    lazy var distanceSortDescriptor: NSSortDescriptor = {
        var sd = NSSortDescriptor(key: "location.distance", ascending: true)
        return sd
    } ()
    
    lazy var priceSortDescriptor: NSSortDescriptor = {
        var sd = NSSortDescriptor(key: "priceInfo.priceCategory", ascending: true)
        return sd
    }()
    
    lazy var offeringDealPredicate: NSPredicate = {
        var pr = NSPredicate(format: "specialCount > 0")
        return pr
    }()
    
    lazy var walkingDistancePredicate: NSPredicate = {
        var pr = NSPredicate(format: "location.distance < 500")
        return pr
    }()
    
    lazy var hasUserTipsPredicate: NSPredicate = {
        var pr = NSPredicate(format: "stats.tipCount > 0")
        return pr
    }()
    
    
    //populate $ yuan count to cell
    
    lazy var cheapVenuePredicate: NSPredicate = {
        var predicate = NSPredicate(format: "priceInfo.priceCategory == %@", "$")
        return predicate
    }()


    //populate $$ yuan venue count to cell
    lazy var moderateVenuePredicate: NSPredicate = {
        var predicate = NSPredicate(format: "priceInfo.priceCategory == %@", "$$")
        return predicate
    } ()
    
    //populate $$$ yuan venue count to cell
    lazy var expensiveVenuePredicate: NSPredicate = {
        var predicate = NSPredicate(format: "priceInfo.priceCategory == %@", "$$$")
        return predicate
    } ()
    
    
    func populateCheapVenueCountLabel() {
        //$ fetch request
        let fetchRequest = NSFetchRequest(entityName: "Venue")
        fetchRequest.resultType = .CountResultType
        fetchRequest.predicate = cheapVenuePredicate
        
        do {
            let results = try coreDataStack.context.executeFetchRequest(fetchRequest) as! [NSNumber]
            let count = results.first!.integerValue
            firstPriceCategoryLabel.text = "\(count) bubble tea places"
            
        } catch let error as NSError {
            print ("Could not fetch \(error), \(error.userInfo)")
        }
    }
    
    func populateModerateVenueCountLabel() {
        // $$ fetch request
        let fetchRequest = NSFetchRequest(entityName: "Venue")
        fetchRequest.resultType = .CountResultType
        fetchRequest.predicate = moderateVenuePredicate
        
        do {
            let results = try coreDataStack.context.executeFetchRequest(fetchRequest) as! [NSNumber]
            let count = results.first!.integerValue
            secondPriceCategoryLabel.text = "\(count) bubble tea places"
        } catch let error as NSError {
            print("Could not fetch \(error), \(error.userInfo)")
        }
    }
    
    func populateExpensiveVenueCountLabel() {
        // $$$ fetch request
        let fetchRequst = NSFetchRequest(entityName: "Venue")
        fetchRequst.predicate = expensiveVenuePredicate
        
        var error: NSError?
        let count = coreDataStack.context.countForFetchRequest(fetchRequst, error: &error)
        
        if count != NSNotFound {
            thirdPriceCategoryLabel.text = "\(count) bubble tea places"
        } else {
            print("Could not fetch \(error), \(error?.userInfo)")
        }
    }
    
    func populateDealsCountLabel()  {
        //1
        let fetchRequest = NSFetchRequest(entityName: "Venue")
        fetchRequest.resultType = .DictionaryResultType
        
        //2
        let sumExpressionDesc = NSExpressionDescription()
        sumExpressionDesc.name = "sumDeals"
        
        //3
        sumExpressionDesc.expression = NSExpression(forFunction: "sum:", arguments: [NSExpression(forKeyPath: "specialCount")])
        sumExpressionDesc.expressionResultType = .Integer32AttributeType
        
        //4
        fetchRequest.propertiesToFetch = [sumExpressionDesc]
        
        //5
        do {
            let results = try coreDataStack.context.executeFetchRequest(fetchRequest) as! [NSDictionary]
            let resultDict = results.first!
            let numDeals = resultDict["sumDeals"]
            numDealsLabel.text = "\(numDeals!) total deals"
        } catch let error as NSError {
            print("Counld not fetch \(error), \(error.userInfo)")
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //count $
        populateCheapVenueCountLabel()
        
        //count $$
        populateModerateVenueCountLabel()
        
        //count $$$
        populateExpensiveVenueCountLabel()
        
        //populate specialCount
        populateDealsCountLabel()
    }
    
  //MARK - UITableViewDelegate methods
  
  override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
  
    let cell = tableView.cellForRowAtIndexPath(indexPath)!
    switch cell {
        //Price section
    case cheapVenueCell:
        selectedPredicate = cheapVenuePredicate
    case moderateVenueCell:
        selectedPredicate = moderateVenuePredicate
    case expensiveVenueCell:
        selectedPredicate = expensiveVenuePredicate
        //Most Popular section
    case offeringDealCell:
        selectedPredicate = offeringDealPredicate
    case walkingDistanceCell:
        selectedPredicate = walkingDistancePredicate
    case userTipsCell:
        selectedPredicate = hasUserTipsPredicate
        //Sort By section
    case nameAZSortCell:
        selectedSortDescriptor = nameSortDescriptor
    case nameZASortCell:
        selectedSortDescriptor = nameSortDescriptor.reversedSortDescriptor as? NSSortDescriptor
    case distanceSortCell:
        selectedSortDescriptor = distanceSortDescriptor
    case priceSortCell:
        selectedSortDescriptor = priceSortDescriptor
    default:
        print("default case")
    }
    
    cell.accessoryType = .Checkmark
  
  }
  
  // MARK - UIButton target action
  
  @IBAction func saveButtonTapped(sender: UIBarButtonItem) {
    delegate!.filterViewController(self, didSelectPredicate: selectedPredicate, sortDescriptor: selectedSortDescriptor)
    
    dismissViewControllerAnimated(true, completion:nil)
  }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值