How to display searched result in Tableviewcontroller? - ios

I Want to display searched core data result in tableviewcontroller. i know how to display core data in tableview but i want specific data to be displayed in tableviewcontroller.
like,when user select specific city from uipickerview, then results from core data displayed in tableviewcontroller according to specific city.
some codes are listed below.
import UIKit
import CoreData
class MenuhospitalTableViewController: UITableViewController {
private var hospitalcoredata: [Hospitalcoredata] = []
var fetchResultController:NSFetchedResultsController!
override func viewDidLoad() {
super.viewDidLoad()
Load menu items from database
if let managedObjectContextt = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContextt {
let fetchRequest = NSFetchRequest(entityName: "Hospitalcoredata")
var e: NSError?
hospitalcoredata = managedObjectContextt.executeFetchRequest(fetchRequest, error: &e) as! [Hospitalcoredata]
if e != nil {
println("Failed to retrieve record: \(e!.localizedDescription)")
}
}
// Make the cell self size
self.tableView.estimatedRowHeight = 66.0
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.layoutIfNeeded()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
return hospitalcoredata.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! MenuhospitalTableViewCell
// Configure the cell...
cell.nameLabel.text = hospitalcoredata[indexPath.row].namee
cell.contactnoLabel.text = hospitalcoredata[indexPath.row].contactnoo
// cell.priceLabel.text = "$\(menuItems[indexPath.row].price as! Double)"
return cell
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/}
and code to find data from city are given below..
let entityDescription =
NSEntityDescription.entityForName("Register",
inManagedObjectContext: managedObjectContext!)
let request = NSFetchRequest()
request.entity = entityDescription
let pred = NSPredicate(format: "(name = %#)", name.text)
request.predicate = pred
var error: NSError?
var objects = managedObjectContext?.executeFetchRequest(request,
error: &error)
if let results = objects {
if results.count > 0 {
let match = results[0] as! NSManagedObject
name.text = match.valueForKey("name") as! String
contactno.text = match.valueForKey("contactno") as! String
altno.text = match.valueForKey("altno") as! String
emailid.text = match.valueForKey("emailid") as! String
textf.text = match.valueForKey("bloodgroup") as! String
textff.text = match.valueForKey("city") as! String
status.text = "Matches found: \(results.count)"
} else {
status.text = "No Match"
}
}
}
I want to mixup this two codes and display core data result accordingly "city" selection.

You should be using a NSFetchedResultsController to populate your table view. Add a dynamic predicate to its fetch request and performFetch before you update your table view.

After lots of try and help of my project guide(prof. chirag pandya),
Answer:
use preparesegue method in first view controller,
give name of push segue,
use segue name,
create predicate in second view controller and use first viewcontroller.
vary useful for searching and sorting
Code:
1)IN (firstviewcontroller)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier=="segueTest"){
var svc = segue.destinationViewController as! MenuhospitalTableViewController
svc.toPass = textff.text
}
}
2)IN (secondviewcontroller)
let pred = NSPredicate(format: "(city = %#)", toPass)
fetchRequest.predicate = pred

Related

How to observe more levels from realtime-database Firebase?

My code do not add values from a while statement of a UIViewController to an Array of a UITableViewController.
This is for a getter function to allow me to see all childrens values under other childrens. Now I'm going to be more specific:
My database node is made of:
Cars -> 0, 1, 2, 3, ... -> Model, Price, ... -> String
As you can see, The number of childs is undefined, so I have to use this control method:
while let child = snapshotChildren.nextObject() as? DataSnapshot {
// Get code node key and save it to cars array
}
First of all, In a loading ViewController, I get code node keys of cars and save them to cars variable of type NSMutableArray of the TableViewController. Then I will do the same thing in the TableViewController to get all indexpath.row childrens value.
let rootRef = Database.database().reference()
let carconditionalRef = rootRef.child("Cars")
carconditionalRef.observe(.value) {(snap: DataSnapshot) in
//Get all the children from snapshot you got back from Firebase
let snapshotChildren = snap.children
//Loop over all children (code) in Firebase
while let child = snapshotChildren.nextObject() as? DataSnapshot {
// Get code node key and save it to cars array
let carvc = Cars_Table();
carvc.cars.add(child.key)
}
}
It results that with this code I still have empty NSMutableArray. How can I solve this?
Edit 1
I fixed that snippet to this:
import UIKit
import FirebaseDatabase
class Loading: UIViewController {
#IBOutlet weak var loading: UIActivityIndicatorView!
var mother: NSMutableArray = []
override func viewDidLoad() {
super.viewDidLoad()
start()
}
func start() {
loading.startAnimating()
if #available(iOS 10.0, *) {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { (timer) in
//let's dance
self.loading.startAnimating()
//call data from database
let rootRef = Database.database().reference()
let conditionalRef = rootRef.child("Cars")
conditionalRef.observe(.value) {(snap: DataSnapshot) in
// Get all the children from snapshot you got back from Firebase
let snapshotChildren = snap.children
// Loop over all children (code) in Firebase
while let child = snapshotChildren.nextObject() as? DataSnapshot {
// Get code node key and save it to cars array
self.mother.add(child.key)
}
self.move()
self.loading.stopAnimating()
self.performSegue(withIdentifier: "loadingfinish", sender: nil)
}
}
} else {
// Fallback on earlier versions
}
}
func move() {
let vc = Cars_Table()
vc.cars = self.mother
}
}
Edit 2
I tried using the recursive method, but it did not work. So I tried one more time with the iterative method this time using the while statement.
Here my new function, this time directly in the Car_TableView.swift:
func loadData() {
//call data from database
let rootRef = Database.database().reference()
let conditionalRef = rootRef.child("Cars")
conditionalRef.observe(.value) {(snap: DataSnapshot) in
// Get all the children from snapshot you got back from Firebase
let snapshotChildren = snap.children
// Loop over all children (code) in Firebase
while let child = snapshotChildren.nextObject() as? DataSnapshot {
// Get code node key and save it to cars array
self.populateTable.append(child.key)
}
var counter = 0
while counter > -self.populateTable.count {
counter -= 1
let rootRef = Database.database().reference()
let userRef = rootRef.child("Cars").child("\(self.populateTable.count+counter)")
userRef.observeSingleEvent(of: .value, with: { snapshot in
let userDict = snapshot.value as! [String: Any]
let model1 = userDict["Model"] as! String
self.model.add(model1)
let detail1 = userDict["Detail"] as! String
self.detailpage.add(detail1)
let year1 = userDict["Year"] as! String
self.year.add(year1)
let carPrice1 = userDict["Price"] as! String
self.price.add(carPrice1)
let carimageURL1 = userDict["imageURL"] as! String
self.imagePathString.add(carimageURL1)
}) //end observeSingleEvent
}
}
}
When I go to do the while, the observeSingleEvent will be work, but it will repeat n^2 times. Why does this happen?
Edit 3
Since the problem seems to be changed since the start, I edited to give all the relevant details. So, the problem now is different and now are two:
When I load database I have n^2 repeated instruction
To see the table filled with database data, I have to touch the tab bar button to the next ViewController then touch the tab bar button to come back on Car_TableView.swift
For the first problem... onestly I have no idea why this happens 😅
For the second problem I thought to use SVProgressHUD to reload data but it doesn't work on loadData() function and if I try the Instance Method tableView.reloadData() it crashes.
variables are all NSMutableArray since that I have to load a lot of stuff that can change in the time
My viewDidLoad() function is very easy as you can see:
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
This is my Table view data source in our Car_TableView.swift:
override func numberOfSections(in tableView: UITableView) -> Int {
return populateTable.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return populateTable.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "carTableCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! Car_Cell
cell.carLabel?.text = "\(self.model[indexPath.row])"
cell.carSubtitle?.text = "Year: \(self.year[indexPath.row]) - Price: \(self.price[indexPath.row])$"
Alamofire.request("\(self.imagePathString[indexPath.row])").response { response in
guard let image = UIImage(data:response.data!) else {
// Handle error
return
}
let imageData = image.jpegData(compressionQuality: 1.0)
cell.carImage.contentMode = .scaleAspectFit
cell.carImage.image = UIImage(data : imageData!)
}
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowcarDetails" {
let myIndexPath = self.tableView.indexPathForSelectedRow!
//save detail page url in UserDefault
let SVDetail = self.detailpage[myIndexPath.row]
let SVDetaildefaults = UserDefaults.standard
SVDetaildefaults.set(SVDetail, forKey: "sv_detail")
SVDetaildefaults.synchronize()
_ = segue.destination
as! Car_Detail
}
}
//SET CELLS SIZE
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch indexPath.row {
case 0,1,2,3,4:
return 100
default:
return 100
}
}
I also re-post the loadData() function because I simplified the operation in an only while statement:
func loadData() {
//call data from database
let rootRef = Database.database().reference()
let conditionalRef = rootRef.child("Cars")
conditionalRef.observe(.value) {(snap: DataSnapshot) in
// Get all the children from snapshot you got back from Firebase
let snapshotChildren = snap.children
// Loop over all children (code) in Firebase
while let child = snapshotChildren.nextObject() as? DataSnapshot {
// Get code node key and save it to cars array
self. populateTable.append(child.key)
let userRef = rootRef.child("Cars").child("\(child.key)")
userRef.observeSingleEvent(of: .value, with: { snapshot in
let userDict = snapshot.value as! [String: Any]
let address1 = userDict["Address"] as! String
self.address.add(address1)
let detail1 = userDict["Detail"] as! String
self.detailpage.add(detail1)
let carnumberOfRooms1 = userDict["numberOfRooms"] as! String
self.numberOfRooms.add(carnumberOfRooms1)
let carPrice1 = userDict["Price"] as! String
self.price.add(carPrice1)
let carimageURL1 = userDict["imageURL"] as! String
self.imagePathString.add(carimageURL1)
}) //end observeSingleEvent
} //end while
} //end snap
}//end func

Could not cast value of type 'NSTaggedPointerString' to an array

I working on a project that is written in swift 3.0. My requirement is to save data that i enter on some text fields and populate one of those attributes in to a table view, and once a row is selected I wants to update that records (re-assign values on my text fields).
However im having an issue with my code when i try to fetch data that i have saved in core data and assigning them in to an array. Basically I have an entity named "Task" and it got three attributes, and since i wants to populate one of those attributes(called "name") that i have saved on core data, to a table view i have written the code as follow. But im getting an exception in the following line in my code saying "Could not cast value of type NSTaggedPointerString (0x10d8f7b90) to NSArray (0x10d8f7c58)".
The error line and the code as bellow.
tasks += expName as! [Task]
Here is my full code:
import UIKit
import CoreData
class TableViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
let appDelegate : AppDelegate = UIApplication.shared.delegate as! AppDelegate
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var tasks = [Task] ()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
//var error : NSError?
let request = NSFetchRequest <NSFetchRequestResult> (entityName: "Task")
request.returnsObjectsAsFaults = false
do {
let results = try context.fetch(request)
// check data existance
if results.count>0 {
print(results.count)
for resultGot in results as! [NSManagedObject]{
if let expName = resultGot.value(forKey:"name"){
print("expence name is :", expName)
tasks += expName as! [Task]
print("my array is : \(tasks)")
}
}
}
}catch{
print("No Data to load")
}
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = tasks [indexPath.row] as? String
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasks.count
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowEditTask"{
let v = segue.destination as! ViewController
let indexPath = self.tableView.indexPathForSelectedRow
let row = indexPath?.row
}
}
The error message says that NSTaggedPointerString (expName) can not be cast to NSArray ([Task])
Your goal is to add all Tasks to the task array if the name property is not nil but you're trying to add the name which causes the error.
Some suggestions:
fetch(context: returns always an array of the NSManagedObject subclass so cast it immediately.
Since you are using NSManagedObject subclass get the name property directly rather than with valueForKey.
The check for > 0 is not needed because the loop will be skipped in case of an empty array.
let results = try context.fetch(request) as! [Task]
// check data existance
print(results.count)
for task in results {
if let expName = task.name {
print("expence name is :", expName)
tasks += task
print("my array is : \(tasks)")
}
}
or shorter
let results = try context.fetch(request) as! [Task]
tasks.filter{ $0.name != nil }
The most efficient way is to filter the tasks before the fetch via an appropriate predicate.

How can I sort and show in correct section of a UITableview in swift using CoreData results

Attached at very bottom of this question is my inventory controller file. My problem is I'm getting duplicate results in all the sections. I narrowed down the reason to
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
My code in that function does not account for how many rows there are in each section. As such I'm just printing out the same duplicate results every section.
The actual question is listed after the images below...
Refer to images below:
I also have the ability to change the index from my settings menu so it can index by numbers, like 0-9. Refer to image below:
That said, I currently load the data from Core Data. Attached is reference image of the entities I use and there relationships.
The Question:
My question is, how can I get the results from coreData to be sorted into the A,B,C type sections or 1,2,3 sections so that navigating the table will be simple.
My hunch is the line that says let inventoryRecords = try moc.executeFetchRequest(inventoryFetchRequest) as? [Inventory] needs a sort descriptor to sort based on how I like, but how I then take the data and put into the correct array structure to split into the sections I need...I have no idea.
globals.swift
import Foundation
import CoreData
//Array of Inventory & Store Core Data Managed Objects
var g_inventoryItems = [Inventory]()
var g_storeList = [Store]()
var g_appSettings = [AppSettings]()
var g_demoMode = false
InventoryController.swift
import UIKit
import CoreData
class InventoryController: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var inventoryTable: UITableView!
var numberIndex = ["0","1","2","3","4","5","6","7","8","9"]
var letterIndex = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]
var moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext //convinience variable to access managed object context
// Start DEMO Related Code
func createInventoryDummyData(number: Int) -> Inventory{
let tempInventory = NSEntityDescription.insertNewObjectForEntityForName("Inventory", inManagedObjectContext: moc) as! Inventory
tempInventory.name = "Test Item # \(number)"
tempInventory.barcode = "00000000\(number)"
tempInventory.currentCount = 0
tempInventory.id = number
tempInventory.imageLargePath = "http://website.tech//uploads/inventory/7d3fe5bfad38a3545e80c73c1453e380.png"
tempInventory.imageSmallPath = "http://website.tech//uploads/inventory/7d3fe5bfad38a3545e80c73c1453e380.png"
tempInventory.addCount = 0
tempInventory.negativeCount = 0
tempInventory.newCount = 0
tempInventory.store_id = 1 //belongs to same store for now
//Select a random store to belong to 0 through 2 since array starts at 0
let aRandomInt = Int.random(0...2)
tempInventory.setValue(g_storeList[aRandomInt], forKey: "store") //assigns inventory to one of the stores we created.
return tempInventory
}
func createStoreDummyData(number:Int) -> Store{
let tempStore = NSEntityDescription.insertNewObjectForEntityForName("Store", inManagedObjectContext: moc) as! Store
tempStore.address = "100\(number) lane, Miami, FL"
tempStore.email = "store\(number)#centraltire.com"
tempStore.id = number
tempStore.lat = 1.00000007
tempStore.lng = 1.00000008
tempStore.name = "Store #\(number)"
tempStore.phone = "123000000\(number)"
return tempStore
}
// End DEMO Related Code
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
print("InventoryController -> ViewDidLoad -> ... starting inits")
//First check to see if we have entities already. There MUST be entities, even if its DEMO data.
let inventoryFetchRequest = NSFetchRequest(entityName: "Inventory")
let storeFetchRequest = NSFetchRequest(entityName: "Store")
do {
let storeRecords = try moc.executeFetchRequest(storeFetchRequest) as? [Store]
if(storeRecords!.count<=0){
g_demoMode = true
print("No store entities found. Demo mode = True. Creating default store entities...")
var store : Store //define variable as Store type
for index in 1...3 {
store = createStoreDummyData(index)
g_storeList.append(store)
}
}
let inventoryRecords = try moc.executeFetchRequest(inventoryFetchRequest) as? [Inventory]
if(inventoryRecords!.count<=0){
g_demoMode = true
print("No entities found for inventory. Demo mode = True. Creating default entities...")
var entity : Inventory //define variable as Inventory type
for index in 1...20 {
entity = createInventoryDummyData(index)
g_inventoryItems.append(entity)
}
print("finished creating entities")
}
}catch{
fatalError("bad things happened \(error)")
}
print("InventoryController -> viewDidload -> ... finished inits!")
}
override func viewWillAppear(animated: Bool) {
print("view appearing")
//When the view appears its important that the table is updated.
//Look at the selected Store & Use the LIST of Inventory Under it.
inventoryTable.reloadData()//this is important to update correctly for changes that might have been made
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
print("inventoryItemControllerPrepareForSegueCalled")
if segue.identifier == "inventoryInfoSegue" {
let vc = segue.destinationViewController as! InventoryItemController
if let cell = sender as? InventoryTableViewCell{
vc.inventoryItem = cell.inventoryItem! //sets the inventory item accordingly, passing its reference along.
}else{
print("sender was something else")
}
}
}
func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
//This scrolls to correct section based on title of what was pressed.
return letterIndex.indexOf(title)!
}
func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
//Use correct index on the side based on settings desired.
if(g_appSettings[0].indextype=="letter"){
return letterIndex
}else{
return numberIndex
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//TODO: Need to figure out how many rows for ...column A,B,C or 1,2,3 based on indexType using~
//To do this we need to organize the inventory results into a section'ed array.
if(g_appSettings[0].selectedStore != nil){
return (g_appSettings[0].selectedStore?.inventories!.count)! //number of rows is equal to the selected stores inventories count
}else{
return g_inventoryItems.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("InventoryTableCell", forIndexPath: indexPath) as! InventoryTableViewCell
if(g_appSettings[0].selectedStore != nil){
//Get the current Inventory Item & Set to the cell for reference.
cell.inventoryItem = g_appSettings[0].selectedStore?.inventories?.allObjects[indexPath.row] as! Inventory
}else{
//This only happens for DEMO mode or first time.
cell.inventoryItem = g_inventoryItems[indexPath.row]//create reference to particular inventoryItem this represents.
}
cell.drawCell() //uses passed inventoryItem to draw it's self accordingly.
return cell
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if(g_appSettings[0].indextype == "letter"){
return letterIndex[section]
}else{
return numberIndex[section]
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if(g_appSettings[0].selectedStore != nil){
if(g_appSettings[0].indextype=="letter"){
return letterIndex.count
}else{
return numberIndex.count
}
}else{
return 1//only one section for DEMO mode.
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//dispatch_async(dispatch_get_main_queue()) {
//[unowned self] in
print("didSelectRowAtIndexPath")//does not recognize first time pressed item for some reason?
let selectedCell = self.tableView(tableView, cellForRowAtIndexPath: indexPath) as? InventoryTableViewCell
self.performSegueWithIdentifier("inventoryInfoSegue", sender: selectedCell)
//}
}
#IBAction func BarcodeScanBarItemAction(sender: UIBarButtonItem) {
print("test of baritem")
}
#IBAction func SetStoreBarItemAction(sender: UIBarButtonItem) {
print("change store interface")
}
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
print("text is changing")
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
print("ended by cancel")
searchBar.text = ""
searchBar.resignFirstResponder()
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
print("ended by search")
searchBar.resignFirstResponder()
}
func searchBarTextDidEndEditing(searchBar: UISearchBar) {
print("ended by end editing")
searchBar.resignFirstResponder()
}
#IBAction func unwindBackToInventory(segue: UIStoryboardSegue) {
print("unwind attempt")
let barcode = (segue.sourceViewController as? ScannerViewController)?.barcode
searchBar.text = barcode!
print("barcode="+barcode!)
inventoryTable.reloadData()//reload the data to be safe.
}
}
//Extention to INT to create random number in range.
extension Int
{
static func random(range: Range<Int> ) -> Int
{
var offset = 0
if range.startIndex < 0 // allow negative ranges
{
offset = abs(range.startIndex)
}
let mini = UInt32(range.startIndex + offset)
let maxi = UInt32(range.endIndex + offset)
return Int(mini + arc4random_uniform(maxi - mini)) - offset
}
}
Update:: **
So I was looking around and found this article (I implemented it).
https://www.andrewcbancroft.com/2015/03/05/displaying-data-with-nsfetchedresultscontroller-and-swift/
I'm really close now to figuring it out. Only problem is I can get it to auto create the sections, but only on another field, like for example store.name, I can't get it to section it into A,B,C sections or 1,2,3.
This is my code for the fetchedResultsController using the methods described in that article.
//Create fetchedResultsController to handle Inventory Core Data Operations
lazy var fetchedResultsController: NSFetchedResultsController = {
let inventoryFetchRequest = NSFetchRequest(entityName: "Inventory")
let primarySortDescriptor = NSSortDescriptor(key: "name", ascending: true)
let secondarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
inventoryFetchRequest.sortDescriptors = [primarySortDescriptor, secondarySortDescriptor]
let frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "store.name",
cacheName: nil)
frc.delegate = self
return frc
}()
Question is what to put for sectionNameKeyPath: now that will make it section it on A B C and I got this !
Found a stackoverflow post very similar to my issue, but need swift answer.
A-Z Index from NSFetchedResultsController with individual section headers within each letter?
Here is another similar article but all objective-c answers.
NSFetchedResultsController with sections created by first letter of a string
Update::
Found another article I think with my exact issue (How to have a A-Z index with a NSFetchedResultsController)
Ok I figured it out, phew was this confusing and took a lot of research.
Okay, so first thing you have to do is create a transient property on the data model. In my case I called it lettersection. To do this in the entity just create a new attribute and call it lettersection and in graph mode if you select it (double click it), you will see option in inspector for 'transient'. This means it won't be saved to the database and is used more for internal reasons.
You then need to manually set up the variable in the extension area of the model definition. Here is how it looks for me.
import Foundation
import CoreData
extension Inventory {
#NSManaged var addCount: NSNumber?
#NSManaged var barcode: String?
#NSManaged var currentCount: NSNumber?
#NSManaged var id: NSNumber?
#NSManaged var imageLargePath: String?
#NSManaged var imageSmallPath: String?
#NSManaged var name: String?
#NSManaged var negativeCount: NSNumber?
#NSManaged var newCount: NSNumber?
#NSManaged var store_id: NSNumber?
#NSManaged var store: Store?
var lettersection: String? {
let characters = name!.characters.map { String($0) }
return characters[0].uppercaseString
}
}
Once you do this, you simply call this new 'lettersection' with the fetchedResultsController like so...
let frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "lettersection",
cacheName: nil)
and everything will work! It sorts by the name of my inventory items, but groups them by the first letters, for a nice A,B,C type list!
"My question is, how can I get the results from coreData to be sorted into the A,B,C type sections or 1,2,3 sections so that navigating the table will be simple."
Using "Store" as your entity and property "name" to be what you want to sort the records by.
override func viewDidLoad() { super.viewDidLoad()
let fetchRequest = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Store", inManagedObjectContext: managedObjectContext)
fetchRequest.entity = entity
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
do {
let foundObjects = try managedObjectContext.executeFetchRequest(fetchRequest)
locations = foundObjects as! [Location]
} catch {
fatalCoreDataError(error) }
}
You are going to use this function to set the number of sections:
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return letterindex.count // if that is how you want to construct sections
}
I learned this from the Ray Wenderlich e-book "iOS Apprentice". From Lesson 3 - MyLocations. Highly recommend this and their e-book book on CoreData.

Why are the same comments showing for different user posts? (iOS, Swift, Parse)

Working on a social iPhone app using Swift (with a Storyboard) and Parse where users can create posts and comment on posts similar to the Facebook iOS app and other social network apps.
The app has an initial, master Home Feed page (which displays user posts) and a detail Reply page (which is supposed to display the comments for a particular post that was selected but is showing the same replies for different posts). Both use the PFTableViewController class and each have their own PFTableViewCell implemented in separate swift files as the prototype cells.
When a user taps on ANY post cell in the Home Feed page, it navigates to the Reply page but shows all existing comments (as well as every new comment) for the post. I am trying to have only the comments for a specific post show when the user selects a particular post from the Home Feed page.
Any idea why this is happening? I greatly appreciate your time and help!
Home Feed page:
class HomeTableVC: PFQueryTableViewController,CLLocationManagerDelegate {
var posts: NSMutableArray! = NSMutableArray()
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("showReplyViewController", sender: self)
}
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?, object: PFObject!) -> PFTableViewCell? {
let cell = tableView!.dequeueReusableCellWithIdentifier("PostCell", forIndexPath: indexPath!) as! PostTableCell
if let userPost : PFObject = self.posts.objectAtIndex(indexPath!.row) as! PFObject {
cell.name.text = object["userName"] as? String
cell.message.text = object["postMessage"] as? String
let dateUpdated = object.createdAt! as NSDate
let dateFormat = NSDateFormatter()
dateFormat.dateFormat = "h:mm a"
cell.dateTime.text = NSString(format: "%#", dateFormat.stringFromDate(dateUpdated)) as String
cell.message.numberOfLines = 0
cell.message.text = userPost.objectForKey("postMessage") as? String
}
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "showReplyViewController") {
let indexPath = self.tableView.indexPathForSelectedRow
let postObject = self.objects![indexPath!.row] as! PFObject
//postObject (on LHS) is the PFObject declared in ResponseViewController
if let destinationVC = segue.destinationViewController as? ReplyTableViewController {
destinationVC.postObject = postObject
}
}
}
}
Reply page:
class ReplyTableViewController: PFQueryTableViewController {
var postObject: PFObject?
var replies: NSMutableArray! = NSMutableArray()
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
replies = NSMutableArray()
var replyQuery = PFQuery(className: "Reply")
replyQuery.addAscendingOrder("createdAt")
replyQuery.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
for object in objects! {
let reply: PFObject = object as! PFObject
self.replies.addObject(reply)
}
let repliesArray: NSArray = self.replies.reverseObjectEnumerator().allObjects
self.replies = NSMutableArray(array: repliesArray)
self.tableView.reloadData()
}
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return replies.count
}
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?, object: PFObject!) -> PFTableViewCell? {
let cell = tableView!.dequeueReusableCellWithIdentifier("replyCell", forIndexPath: indexPath!) as! ReplyTableViewCell
let replyObject: PFObject = self.replies.objectAtIndex(indexPath!.row) as! PFObject
cell.replyMessageLabel.text = replyObject.objectForKey("replyMessage") as? String
var queryUser: PFQuery = PFUser.query()!
queryUser.whereKey("objectId", equalTo: (replyObject.objectForKey("replyUser")?.objectId)!)
queryUser.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
let user: PFUser = (objects! as NSArray).lastObject as! PFUser
cell.replyAuthorLabel.text = user.username
}
}
return cell
}
}
In your segue you need to tell the destination ViewController which post to show replies for.
Add this to the bottom of your segue (exactly where your comment is):
if let destinationVC = segue.destinationViewController as? ReplyTableViewController{
destinationVC.postObject = postObject
}
And in ReplyTableViewController you need a postObject variable so that the code in the segue works. At the top of your ReplyTableViewController put:
var postObject = PFObject()
It looks like the postObject should be used somewhere in your PFQuery() to filter the replies, but I am not familiar with it.
I found a solution to my own problem!
I have updated the Reply page to use UITableViewController instead of PFTableViewController and updated the storyboard correspondingly (I made the necessary changes in the code and in the Storyboard to comply with the constraints of UITableViewController, etc).
I implemented a PFQuery with the appropriate constraints to fetch all the replies for a given post (only) by writing something similar to the following:
query.whereKey("parent", equalTo: aPost)
// Finds objects *asynchronously* and call the given block with the results.
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
// if there is no error, for each object in `objects`,
// assign the given object to a PFObject
// add the object to an array that will store all of the applicable replies for the post
// ...
}

How do you fetch NSSet from NSManagedObject in Swift?

I have a one to many relationship from Set to Card for a basic Flashcard App modelled in my Core Data.
Each Set has a set name, set description, and a relationships many card1s. Each Card1 has a front, back, and photo. In my table view, I've managed to retrieve all saved Sets from core data and display them. Now I want to fetch each Set's cards when a user clicks on the appropriate cell in my next view controller.
This is my code for the table view controller:
// MARK: Properties
var finalArray = [NSManagedObject]()
override func viewDidLoad() {
getAllSets()
println(finalArray.count)
}
func getAllSets() {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
let fetchRequest = NSFetchRequest(entityName:"Set")
var error: NSError?
let fetchedResults = managedContext.executeFetchRequest(fetchRequest,error: &error) as? [NSManagedObject]
println("Am in the getCardSets()")
if let results = fetchedResults {
finalArray = results
println(finalArray.count)
}
else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}
// MARK: Displaying the data
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return finalArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! SetTableViewCell
let sets = finalArray[indexPath.row]
cell.setName.text = sets.valueForKey("setName")as? String
cell.setDescription.text = sets.valueForKey("setDescription")as? String
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowDetail" {
let dest = segue.destinationViewController as! Display
// Get the cell that generated this segue.
if let selectedCell = sender as? SetTableViewCell {
let indexPath = tableView.indexPathForCell(selectedCell)!
let selectedSet = finalArray[indexPath.row]
dest.recievedSet = selectedSet
}
}
}
In my destination view controller, how would I go about retrieving all the cards in that the recievedSet? I've tried converting the NSSet to an array and casting it to a [Card1] array but when I attempt to display the first Card1's front String property onto the label, the app crashes, giving me the error
CoreData: error: Failed to call designated initializer on NSManagedObject class 'NSManagedObject'
fatal error: Array index out of range
This is my code for the detailed viewController.
#IBOutlet weak var front: UILabel!
var finalArray = [Card1]()
finalArray = retrievedSet.allObjects as![Card1]
front.text = finalArray[0].front
Give your detail controller a property of type CardSet (I use "CardSet" because "Set" is a Swift built-in type name). You pass the selected set to this controller.
You could have a property by which you sort, or generate an array without a particular order with allObjects.
var cardArray = [Card1]()
var cardSet: CardSet?
viewDidLoad() {
super.viewDidLoad()
if let validSet = cardSet {
cardArray = validSet.cards.allObjects as! [Card1]
}
}
Your code is not working because finalArray is of type [CardSet], so finalArray[indexPath.row] is of type CardSet which is not transformable into type NSSet. Rather the relationship to Card1s is the NSSet you are looking for.
Finally, I recommend to give the detail controller a NSFetchedResultsController, have an attribute to sort by and use the passed CardSet in the fetched results controller's predicate.

Resources