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