how to load the Core Data in background thread - swift

i have some big data in my Core Data store
how can i load this data in background thread?
func connectionCoreData() {
let fetchRequest = NSFetchRequest<PersonalClass>(entityName: "PersonalBase")
let sortDescriptor = NSSortDescriptor(key: "personName", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
if let managerObjectContext = (UIApplication.shared.delegate as? AppDelegate)?.managedObjectContext {
fetchResultController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managerObjectContext, sectionNameKeyPath: nil, cacheName: nil)
fetchResultController.delegate = self
do {
try fetchResultController.performFetch()
personalArray = fetchResultController.fetchedObjects!
self.tableView.reloadData()
} catch {
print(error)
}
}
}
i need add core data load in background thread and then update my tableView

First, you should put in mind that a managedObjectContext runs on a single thread. And you should access/edit the loaded objects on the same thread.
In your case, you will interact with the objects that you are going to load on the main thread. For example, the loaded database objects will fill a tableView and this should be done on the main thread. This forces the managedObjectContext to be of MainContextType which runs on the main thread.
http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html
You should not be afraid of running the NSFetchedResultsController on the main thread because it loads the objects in batches. However, you aren't using the FetchedResults Controller as it should. You shouldn't have these two lines in your code.
personalArray = fetchResultController.fetchedObjects!
self.tableView.reloadData()
You should access the loaded object using this method fetchResultController .objectAtIndexPath(indexPath).
This is an example of how to use the NSFetchedResultsController
class ViewController: UITableViewController NSFetchedResultsControllerDelegate{
lazy var fetchedResultsController: NSFetchedResultsController = {
let fetchRequest = ... //Create the fetch request
let sortDescriptor = ... //Create a sortDescriptor
let predicate = ...//Create the predicate if you want to filter the results
fetchRequest.predicate = predicate
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: mainContext, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController.delegate = self
return fetchedResultsController
}()
override fun viewDidLoad(){
super.viewDidLoad()
do {
try self.fetchedResultsController.performFetch()
}catch {
print(error)
}
}
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch (type) {
case .Insert:
if let indexPath = newIndexPath {
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
break;
case .Delete:
if let indexPath = indexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
break;
case .Update:
if let indexPath = indexPath {
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
break;
case .Move:
if let indexPath = indexPath, newIndexPath = newIndexPath {
tableView.moveRowAtIndexPath(indexPath, toIndexPath: newIndexPath)
}
break;
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = fetchedResultsController.sections {
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
return 0
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let obj = fetchedResultsController.objectAtIndexPath(indexPath){
....
}
}

Related

UITableView didSelectRowAt returns wrong tableViewCell at indexPath

I a making a todo plan app, and I have encountered a serious bug in my tableViewCell. Whenever I select a row more than once, the wrong indexPath at row is called.
If I comment out my resultsController.delegate the app works normally. However I need the results controller delegate for to update the table view every time a new plan is made.
override func viewDidLoad() {
super.viewDidLoad()
// Create Request
let request:NSFetchRequest<Plan> = Plan.fetchRequest()
let sortDescriptors = NSSortDescriptor(key: "date", ascending: true) // make request more specific
request.sortDescriptors = [sortDescriptors]
// Init our results controller
resultsController = NSFetchedResultsController(
fetchRequest: request,
managedObjectContext: coreDataStack.managedContext,
sectionNameKeyPath: nil,
cacheName: nil
)
resultsController.delegate = self
// Perform fetch request
do {
try resultsController.performFetch()
} catch {
print("Perform fetch error \(error)")
}
}
Here are the relevant TableViewDelegates
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "showNewPlan", sender: tableView.cellForRow(at: indexPath))
}
Also i have also added my prepare function for navigation.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let _ = sender as? UIBarButtonItem , let vc = segue.destination as? NewPlanViewController {
vc.managedContext = resultsController.managedObjectContext
}
if let cell = sender as? UITableViewCell , let vc = segue.destination as? NewPlanViewController {
vc.managedContext = resultsController.managedObjectContext
if let indexPath = tableView.indexPath(for: cell) {
let plan = resultsController.object(at: indexPath)
vc.plan = plan
}
}
}
The resultsControllerDelegate is how I update my tableViewCells.
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .automatic)
}
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .automatic)
}
case .update:
if let indexPath = indexPath, let cell = tableView.cellForRow(at: indexPath) {
let plan = resultsController.object(at: indexPath)
cell.textLabel?.text = plan.title
}
default:
break
}
}

Populate TableView from CoreData with NSFetchRequest

I need to populate my table view with the data from my core data.
I can display my rows but, I can't populate my tableview!
I use Swift 3 and Xcode 8.
This is my code, in this controller I have to display the list.
class iTableViewController: UITableViewController, NSFetchedResultsControllerDelegate{
#IBOutlet var iTableView: UITableView!
override func viewDidLoad(){
super.viewDidLoad()
iTableView.dataSource = self
iTableView.delegate = self
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managerContext = appDelegate.persistentContainer.viewContext
let interventsFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Intervento")
do{
//results
let fetchedIntervents = try managerContext.fetch(interventsFetch) as! [Intervento]
for interv in fetchedIntervents{
print(interv.stampRow())
}
NSLog(String(fetchedIntervents.count))
}catch{
fatalError("Failed to fetch employees: \(error)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//number of sections
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//numer of rows
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
//cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "cell")
cell.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
//print the example cell
cell.textLabel?.text = "title"
cell.detailTextLabel?.text = "subtitle"
return cell
}
//swipe and delete
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath){
if(editingStyle == UITableViewCellEditingStyle.delete){
//TODO
}
}
// click cell
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
performSegue(withIdentifier: "segueDetail", sender: self)
}
override func viewDidAppear(_ animated: Bool){
//iTableView.reloadData()
}
override func viewWillDisappear(_ animated: Bool) {
//iTableView.reloadData()
//fetchData()
}
}
this is my class
extension Intervento {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Intervento> {
return NSFetchRequest<Intervento>(entityName: "Intervento");
}
#NSManaged public var cliente: Int32
#NSManaged public var dataFine: String?
#NSManaged public var dataInizio: String?
#NSManaged public var descrizione: String?
#NSManaged public var foto: String?
#NSManaged public var id: Int32
#NSManaged public var stato: String?
#NSManaged public var tecnico: String?
func stampRow()->String{
let v1 = String(id) + " " + String(cliente) + " "
let v2 = dataInizio! + " " + dataFine! + " "
let v3 = stato! + " " + tecnico! + " " + descrizione!
return v1 + v2 + v3
}
}
and this is the my code for save the data (it works)
#IBAction func addButton(_ sender: Any){
//genero la data
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy HH:mm"
let strDate = dateFormatter.string(from: myDatePicker.date)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managerContext = appDelegate.persistentContainer.viewContext
let newInt = NSEntityDescription.insertNewObject(forEntityName: "Intervento", into: managerContext)
newInt.setValue(1, forKey: "id")
newInt.setValue(strDate, forKey: "dataInizio")
newInt.setValue("", forKey: "dataFine")
newInt.setValue(2, forKey: "cliente")
newInt.setValue("Matt", forKey: "tecnico")
newInt.setValue(stato[1], forKey: "stato")
newInt.setValue("", forKey: "descrizione")
newInt.setValue("", forKey: "foto")
do{
try managerContext.save()
print("saved")
}catch let error as NSError{
//errore salvataggio
print("Could not fetch \(error), \(error.userInfo)")
}
}
First of all, your class should implement this protocols:
UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate
You can load data from a persistent storage with this code:
//load data
//persistant container
let persistentContainer = NSPersistentContainer.init(name: "Model")
//
lazy var fetchedResultsController: NSFetchedResultsController<Intervento> = {
// Create Fetch Request
let fetchRequest: NSFetchRequest<Intervento> = Intervento.fetchRequest()
// Configure Fetch Request
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "dataInizio", ascending: false)]
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managerContext = appDelegate.persistentContainer.viewContext
// Create Fetched Results Controller
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managerContext, sectionNameKeyPath: nil, cacheName: nil)
// Configure Fetched Results Controller
fetchedResultsController.delegate = self
return fetchedResultsController
}()
//carica
func load(){
//persistant container
persistentContainer.loadPersistentStores { (persistentStoreDescription, error) in
if let error = error {
print("Unable to Load Persistent Store")
print("\(error), \(error.localizedDescription)")
} else {
do {
try self.fetchedResultsController.performFetch()
} catch {
let fetchError = error as NSError
print("Unable to Perform Fetch Request")
print("\(fetchError), \(fetchError.localizedDescription)")
}
}
}
}
override func viewDidLoad(){
super.viewDidLoad()
iTableView.delegate = self
iTableView.dataSource = self
// trigger load
load()
}
Your data will be fetched and will show up because you have delegated iTableViewController by this line of code:
// Configure Fetched Results Controller
fetchedResultsController.delegate = self
In order to make this work, your ViewController must conform to NSFetchedResultsControllerDelegate protocol, here you can find a possibile implementation:
extension iTableViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
iTableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
iTableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch (type) {
case .insert:
if let indexPath = newIndexPath {
iTableView.insertRows(at: [indexPath], with: .fade)
}
break;
case .delete:
if let indexPath = indexPath {
iTableView.deleteRows(at: [indexPath], with: .fade)
}
break;
case .update:
if let indexPath = indexPath, let cell = iTableView.cellForRow(at: indexPath) {
configureCell(cell, at: indexPath)
}
break;
case .move:
if let indexPath = indexPath {
iTableView.deleteRows(at: [indexPath], with: .fade)
}
if let newIndexPath = newIndexPath {
iTableView.insertRows(at: [newIndexPath], with: .fade)
}
break;
}
}
}
In conclusion, this is the full implementation of your iTableViewController (I have included also the necessary code for deleting an row):
class iTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
#IBOutlet var iTableView: UITableView!
//load data
//persistant container
let persistentContainer = NSPersistentContainer.init(name: "Model")
//
lazy var fetchedResultsController: NSFetchedResultsController<Intervento> = {
// Create Fetch Request
let fetchRequest: NSFetchRequest<Intervento> = Intervento.fetchRequest()
// Configure Fetch Request
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "dataInizio", ascending: false)]
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managerContext = appDelegate.persistentContainer.viewContext
// Create Fetched Results Controller
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managerContext, sectionNameKeyPath: nil, cacheName: nil)
// Configure Fetched Results Controller
fetchedResultsController.delegate = self
return fetchedResultsController
}()
//carica
func load(){
//persistant container
persistentContainer.loadPersistentStores { (persistentStoreDescription, error) in
if let error = error {
print("Unable to Load Persistent Store")
print("\(error), \(error.localizedDescription)")
} else {
do {
try self.fetchedResultsController.performFetch()
} catch {
let fetchError = error as NSError
print("Unable to Perform Fetch Request")
print("\(fetchError), \(fetchError.localizedDescription)")
}
}
}
}
override func viewDidLoad(){
super.viewDidLoad()
iTableView.delegate = self
iTableView.dataSource = self
// trigger load
load()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//numero di sezioni
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//numero di righe
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let quotes = fetchedResultsController.fetchedObjects else { return 0 }
return quotes.count
}
//gestisco la cella
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//genero la cella
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "cell")
configureCell(cell, at:indexPath)
return cell
}
//swipe and delete
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath){
if(editingStyle == .delete){
// Fetch Quote
let quote = fetchedResultsController.object(at: indexPath)
// Delete Quote
quote.managedObjectContext?.delete(quote)
}
}
// click cell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
performSegue(withIdentifier: "segueDetail", sender: self)
}
func configureCell(_ cell: UITableViewCell, at indexPath: IndexPath) {
//get intervento
let intervento = fetchedResultsController.object(at: indexPath)
//fill the cell
cell.textLabel?.text = intervento.dataInizio
cell.detailTextLabel?.text = "SOME_THING"
}
//quando appare aggiorno la table view
override func viewDidAppear(_ animated: Bool){
self.iTableView.reloadData()
}
}
extension iTableViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
iTableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
iTableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch (type) {
case .insert:
if let indexPath = newIndexPath {
iTableView.insertRows(at: [indexPath], with: .fade)
}
break;
case .delete:
if let indexPath = indexPath {
iTableView.deleteRows(at: [indexPath], with: .fade)
}
break;
case .update:
if let indexPath = indexPath, let cell = iTableView.cellForRow(at: indexPath) {
configureCell(cell, at: indexPath)
}
break;
case .move:
if let indexPath = indexPath {
iTableView.deleteRows(at: [indexPath], with: .fade)
}
if let newIndexPath = newIndexPath {
iTableView.insertRows(at: [newIndexPath], with: .fade)
}
break;
}
}
}
Don't forget to use always the same context when you make operations with your data (for example when you add new data).
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managerContext = appDelegate.persistentContainer.viewContext
For a full example you should look here: https://github.com/bartjacobs/ExploringTheFetchedResultsControllerDelegateProtocol
Swift 3.3
lazy var fetchedResultsController: NSFetchedResultsController<SavedContact> = {
let fetchReqest: NSFetchRequest<SavedContact> = SavedContact.fetchRequest()
fetchReqest.sortDescriptors = [NSSortDescriptor(key: "id", ascending: true)]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchReqest, managedObjectContext: Database.shared.mainManagedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController.delegate = self
return fetchedResultsController
}()
override func viewDidLoad() {
super.viewDidLoad()
self.performFetch()
}
func performFetch() {
do {
try self.fetchedResultsController.performFetch()
} catch {
Debug.Log(message: "Error in fetching Contact \(error)")
}
}
//MARK: - NSFetchResultcontrollerDelegate
extension YourViewC: NSFetchedResultsControllerDelegate {
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.reloadData()
}
}
// Database Class
private let _singletonInstance: Database = Database()
class Database: NSObject {
class var shared: Database {
return _singletonInstance
}
override init() {
super.init()
}
// Main Managed Object Context
lazy var mainManagedObjectContext: NSManagedObjectContext = {
let coordinator = self.persistentStoreCoordinator
var mainManagedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
mainManagedObjectContext.persistentStoreCoordinator = coordinator
return mainManagedObjectContext
}()
// Persistent Store Coordinator
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
let mOptions = [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true]
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.appendingPathComponent("AppName.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
do {
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: mOptions)
} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject?
dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject?
dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
abort()
}
return coordinator
}()
// DB Directory and Path
lazy var applicationDocumentsDirectory: URL = {
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentDirectoryURL = urls[urls.count - 1] as URL
let dbDirectoryURL = documentDirectoryURL.appendingPathComponent("DB")
if FileManager.default.fileExists(atPath: dbDirectoryURL.path) == false{
do{
try FileManager.default.createDirectory(at: dbDirectoryURL, withIntermediateDirectories: false, attributes: nil)
}catch{
}
}
return dbDirectoryURL
}()
}

Swift CoreData saving and fetching from a relationship

I have coredata in my swift app. I am able to get the two textfields.text to save to core data and the fetch it, but those 2 properties are part of 1 entity. I also have a few other entities with relationships all set up.
When I save to core data what happens is the 2 strings come through, but the relationship strings are nil and the app crashes.
Here is some of my set up
// MARK: NSFetchedResultsControllerDelegate
extension SavedPalauttesViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch(type) {
case .insert:
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
break
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
break
case.update:
if let indexPath = indexPath{
let cell = tableView.cellForRow(at: indexPath) as! LocalPalautteTableViewCell
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
}
break
case .move:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
break
}
}
func attemptFetch() {
let fetchRequest: NSFetchRequest<Palautte> = Palautte.fetchRequest()
let nameSort = NSSortDescriptor(key: "name", ascending: false)
fetchRequest.sortDescriptors = [nameSort]
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
self.controller = controller
do {
try controller.performFetch()
} catch {
let error = error as NSError
print("\(error)")
}
}
}
Now the category of type string and name of type string work fine
but when I try to save any of the background colors or foreground...I get nil and a crash
this is part of how I am attempting to save to core data
palauttePalautte.category = finalPalautteCategoryValue ?? ""
palauttePalautte.toBackgroundColor?.redValue = String(Int(redBackgroundColorValue))
but on the 2nd vc when I try to get a value for it I crash
let backRed = palautte.toBackgroundColor?.redValue ?? ""
What am I doing wrong?

How do I pull the first result from some json?

I'm using Swift, and I haven't had this problem before. This is my code:
var item = jsonResult["items"][0] as! NSDictionary
I get an error for the whole line saying
Cannot subscript a value of type 'AnyObject?' with an index of type 'Int'
Any help?
The rest of my code is here:
import UIKit
import CoreData
class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate {
var detailViewController: DetailViewController? = nil
var managedObjectContext: NSManagedObjectContext? = nil
override func awakeFromNib() {
super.awakeFromNib()
if UIDevice.currentDevice().userInterfaceIdiom == .Pad {
self.clearsSelectionOnViewWillAppear = false
self.preferredContentSize = CGSize(width: 320.0, height: 600.0)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let urlPath = "https://www.googleapis.com/blogger/v3/blogs/10861780/posts?key=AIzaSyDZ86TotGGMQpRJ6GCNrCMDMaqwbQAbfz4"
let url = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
if (error != nil) {
println(error)
} else {
let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error:nil) as!
NSDictionary
var item = jsonResult["items"][0] as! NSDictionary
}
})
task.resume()
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = controllers[controllers.count-1].topViewController as? DetailViewController
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func insertNewObject(sender: AnyObject) {
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) as! NSManagedObject
// If appropriate, configure the new managed object.
// Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
newManagedObject.setValue(NSDate(), forKey: "timeStamp")
// Save the context.
var error: NSError? = nil
if !context.save(&error) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//println("Unresolved error \(error), \(error.userInfo)")
abort()
}
}
// MARK: - Segues
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
// MARK: - Table View
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel!.text = "Blog Item"
return cell
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let context = self.fetchedResultsController.managedObjectContext
context.deleteObject(self.fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject)
var error: NSError? = nil
if !context.save(&error) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//println("Unresolved error \(error), \(error.userInfo)")
abort()
}
}
}
func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) {
let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject
cell.textLabel!.text = object.valueForKey("timeStamp")!.description
}
// MARK: - Fetched results controller
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest = NSFetchRequest()
// Edit the entity name as appropriate.
let entity = NSEntityDescription.entityForName("Event", inManagedObjectContext: self.managedObjectContext!)
fetchRequest.entity = entity
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "timeStamp", ascending: false)
let sortDescriptors = [sortDescriptor]
fetchRequest.sortDescriptors = [sortDescriptor]
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: "Master")
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//println("Unresolved error \(error), \(error.userInfo)")
abort()
}
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController? = nil
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case .Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
default:
return
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Update:
self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!)
case .Move:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
default:
return
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.endUpdates()
}
/*
// Implementing the above methods to update the table view in response to individual changes may have performance implications if a large number of changes are made simultaneously. If this proves to be an issue, you can instead just implement controllerDidChangeContent: which notifies the delegate that all section and object changes have been processed.
func controllerDidChangeContent(controller: NSFetchedResultsController) {
// In the simplest, most efficient, case, reload the table view.
self.tableView.reloadData()
}
*/
}
I can't be sure what's going on because you haven't provided much to go on.
You didn't provide the code that deserialized your JSON data into objects. It's probably a collection of Foundation objects.
It sounds like jsonResult is an NSDictionary. If so, the value for each key is going to be of type AnyObject? (optional AnyObject.)
You will need to cast it to the array type you're expecting:
if let array = jsonResult["items"] as? NSArray,
let item = array[0] as? NSDictionary
{
//Unwrapped the dictionary inside the array inside the dictionary.
}
else
{
//There's something rotten in the state of Denmark
}
var item = ( jsonResult["items"] as! [ NSDictionary ] )[ 0 ]
Or restrict keys to String type:
var item = ( jsonResult["items"] as! [ [ String: AnyObject ] ] )[ 0 ]
This will various according to the json that you get. It will be better if you post the json result here.
jsonResult will definitely be a NSDictionary. But tbe type of jsonResult["item"] is still unclear to me.
If the jsonResult["item"] is a NSDictionary, I guess there is not such a saying as "the first element". NSDictionary is a key-value pair, I'm not sure whether it has some sort of sequence for it.
If jsonResult["item"] is an array, you should get it and set it to a NSArray. Use let itemArray = jsonResult["item"] as! NSArray instead. Then you can use itemArray[0] to get the first item.
So please check what jsonResult["item"] exactly is.
Hope this helps.

Swift UISearchController wired up in Core Data Project, app runs, but search not updating

I have been wrestling the past few days with creating a filter in a Swift project for a TableViewController that uses Core Data. I finally figured out I need to use a UISearchController, create an NSPredicate for the searchController.searchBar, etc.
I found this post EXTREMELY helpful, but after modeling my TVC after this project, I find "all the lights are on, but nobody's home". I can search, predicate is created in searchBar, add, remove, etc. but the cells don't update for the search. I'm missing something, but I don't know what.
Here are the relevant portions of my code.
class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate, UISearchControllerDelegate, UISearchResultsUpdating
// Properties that get instantiated later
var detailViewController: DetailViewController? = nil
var addNoteViewController:AddNoteViewController? = nil // I added this
var managedObjectContext: NSManagedObjectContext? = nil
// Added variable for UISearchController
var searchController: UISearchController!
var searchPredicate: NSPredicate? // I added this. It's optional on and gets set later
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.leftBarButtonItem = self.editButtonItem()
if let split = self.splitViewController {
let controllers = split.viewControllers
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
self.detailViewController = controllers[controllers.count-1].topViewController as? DetailViewController
}
// UISearchController setup
searchController = UISearchController(searchResultsController: nil)
searchController.dimsBackgroundDuringPresentation = false
searchController.searchResultsUpdater = self
searchController.searchBar.sizeToFit()
self.tableView.tableHeaderView = searchController?.searchBar
self.tableView.delegate = self
self.definesPresentationContext = true
}
// MARK: - UISearchResultsUpdating Delegate Method
// Called when the search bar's text or scope has changed or when the search bar becomes first responder.
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchText = self.searchController?.searchBar.text // steve put breakpoint
println(searchController.searchBar.text)
if let searchText = searchText {
searchPredicate = NSPredicate(format: "noteBody contains[c] %#", searchText)
self.tableView.reloadData()
println(searchPredicate)
}
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
var note: Note
if searchPredicate == nil {
note = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Note
} else {
let filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() {
return self.searchPredicate!.evaluateWithObject($0)
}
note = filteredObjects![indexPath.row] as! Note
}
let context = self.fetchedResultsController.managedObjectContext
context.deleteObject(note)
var error: NSError? = nil
if !context.save(&error) {
abort()
}
}
}
// MARK: - Fetched results controller
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest = NSFetchRequest()
// Edit the entity name as appropriate.
let entity = NSEntityDescription.entityForName("Note", inManagedObjectContext: self.managedObjectContext!)
fetchRequest.entity = entity
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "noteTitle", ascending: false)
let sortDescriptors = [sortDescriptor]
fetchRequest.sortDescriptors = [sortDescriptor]
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: "Master")
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
println("Unresolved error \(error), \(error?.userInfo)")
abort()
}
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController? = nil
func controllerWillChangeContent(controller: NSFetchedResultsController) {
// self.tableView.beginUpdates() // ** original code, change if doesn't work** steve put breakpoint here
// ANSWER said this section is redundant, but keeping it b/c it doesn't crash
if searchPredicate == nil {
tableView.beginUpdates()
} else {
(searchController.searchResultsUpdater as! MasterViewController).tableView.beginUpdates()
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
var tableView = UITableView()
if searchPredicate == nil {
tableView = self.tableView
} else {
tableView = (searchController.searchResultsUpdater as! MasterViewController).tableView
}
switch type {
case .Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
default:
return
}
}
I think my problem "lives" here in this section. Autocomplete has been my friend up to here, but I'm don't see "searchIndex" referenced in AutoComplete. I think I'm missing something, but I'm not sure what or how.
If you've made it this far, thanks for reading. Here's the GitHub repo for the branch I'm working on.
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
var tableView = UITableView()
if self.searchPredicate == nil {
tableView = self.tableView
} else {
tableView = (self.searchController.searchResultsUpdater as! MasterViewController).tableView
}
switch type {
case .Insert:
println("*** NSFetchedResultsChangeInsert (object)")
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Delete:
println("*** NSFetchedResultsChangeDelete (object)")
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Update:
println("*** NSFetchedResultsChangeUpdate (object)")
// TODO: need fix this
// ORIGINAL CODE
// self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!) // original code
// PROSPECTIVE SOLUTION CODE
println("*** NSFetchedResultsChangeUpdate (object)")
if searchPredicate == nil {
self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!) // original code
} else {
// Should search the do something w/ the UISearchControllerDelegate or UISearchResultsUpdating
// Instead of "indexPath", it should be "searchIndexPath"--How?
let cell = tableView.cellForRowAtIndexPath(searchIndexPath) as LocationCell // My cell is a vanilla cell, not a xib
let location = controller.objectAtIndexPath(searchIndexPath) as Location // My object is a "Note"
cell.configureForLocation(location) // This is from the other guy's code, don't think it's applicable to me
}
case .Move:
println("*** NSFetchedResultsChangeMove (object)")
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
default:
return
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
if self.searchPredicate == nil {
self.tableView.endUpdates()
} else {
println("controllerDidChangeContent")
(self.searchController.searchResultsUpdater as! MasterViewController).tableView.endUpdates()
}
}
Edit: Per #pbasdf, I'm adding the TableView methods.
// MARK: - Table View
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.fetchedResultsController.sections?.count ?? 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.searchPredicate == nil {
let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo
return sectionInfo.numberOfObjects
}
let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo
return sectionInfo.numberOfObjects
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
self.configureCell(cell, atIndexPath: indexPath)
return cell
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
The problems lie in your tableView datasource methods: numberOfSectionsInTableview:, tableView:numberOfRowsInSection:, and tableView:cellForRowAtIndexPath:. You need each of those methods to return different results if the searchPredicate is not nil - much like your tableView:commitEditingStyle: method does. I would make filteredObjects an instance property (defined at the start of the class) so that all those methods can access it:
var filteredObjects : [Note]? = nil
Now, when the search text changes, you want to rebuild the filteredObjects array. So in updateSearchResultsForSearchController, add a line to recompute it based on the new predicate:
if let searchText = searchText {
searchPredicate = NSPredicate(format: "noteBody contains[c] %#", searchText)
filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() {
return self.searchPredicate!.evaluateWithObject($0)
} as [Note]?
self.tableView.reloadData()
println(searchPredicate)
}
I would also recommend (for simplicity) that when you activate the search, the results are displayed all in one section (otherwise you have to work out how many sections your filtered results fall into - possible but tiresome):
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if searchPredicate == nil {
return self.fetchedResultsController.sections?.count ?? 0
} else {
return 1
}
}
Next, if the searchPredicate is not nil, the number of rows in the section will be the count of filteredObjects:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.searchPredicate == nil {
let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo
return sectionInfo.numberOfObjects
} else {
return filteredObjects?.count ?? 0
}
}
Finally, if the searchPredicate is not nil, you need to build the cell using the filteredObjects data, rather than the fetchedResultsController:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
if searchPredicate == nil {
self.configureCell(cell, atIndexPath: indexPath)
return cell
} else {
// configure the cell based on filteredObjects data
...
return cell
}
}
Not sure what labels etc you have in your cells, so I leave it to you to sort that bit.