How to display many to many relationship with nsfetchedresultcontroller? - core-data

If I use a one to many, I can display all the associated NSSet records but many to many doesn't seem to work the same.
On my segue I pass the gymnast to a VC in which I would like to display all associated meets which works fine in a one to many.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let objs = fetchedResultsController.fetchedObjects where objs.count > 0 {
let item = objs[indexPath.row] as! Gymnast
performSegueWithIdentifier("displayMeets", sender: item)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "displayMeets" {
let vc = segue.destinationViewController as! SelectMeetVC
vc.selectedGymnast = sender as? Gymnast
}
}
I get a crash unless I comment out the NSPREDICATE
func loadFetchRequest() {
self.fetchedResultsController = nil
let fetchRequest = NSFetchRequest(entityName: "Meet")
let sortDescriptor1 = NSSortDescriptor(key: "meetName", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor1]
//let filter = NSPredicate(format: "gymnasts.fullName == %#", "\(selectedGymnast.fullName!)")
//fetchRequest.predicate = filter
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: AD.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
fetchedResultsController = controller
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("Error retrieving core data")
}
}

Because the gymnasts relationship is to-many, you need to amend the predicate so that it selects a Meet if any of its gymnasts matches:
let filter = NSPredicate(format: "ANY gymnasts.fullName == %#", "\(selectedGymnast.fullName!)")
Note also that there's no need to use the fullName attribute - you can match the Gymnast object itself:
let filter = NSPredicate(format: "ANY gymnasts == %#", selectedGymnast)

Related

fetch core data string and place in a label (Swift4)

I am trying to call 2 different core data strings and place them each on separate labels. Right now I am getting the error Cannot invoke initializer for type 'init(_:)' with an argument list of type '([NSManagedObject])'. This error is coming from j1.text = String(itemsName). I added both view controllers for saving and displaying.
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet var j1 : UITextField!
#IBOutlet var j2 : UITextField!
#IBAction func save(){
let appD = UIApplication.shared.delegate as! AppDelegate
let context = appD.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Team", in : context)!
let theTitle = NSManagedObject(entity: entity, insertInto: context)
theTitle.setValue(j1.text, forKey: "score")
theTitle.setValue(j2.text, forKey: "alba")
do {
try context.save()
}
catch {
print("Tom Corley")
}
}}
class twoVC: UIViewController {
#IBOutlet var j1 : UILabel!
#IBOutlet var j2 : UILabel!
var itemsName : [NSManagedObject] = []
var itemsName2 : [NSManagedObject] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let appD = UIApplication.shared.delegate as! AppDelegate
let context = appD.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Team")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "score", ascending: true)]
let fetchRequest2 = NSFetchRequest<NSManagedObject>(entityName: "Team")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "alba", ascending: true)]
do {
itemsName = try context.fetch(fetchRequest)
itemsName2 = try context.fetch(fetchRequest2)
if let score = itemsName[0].value(forKey: "score") {
j1.text = (score as! String)
}
if let alba = itemsName2[0].value(forKey: "alba") {
j2.text = (alba as? String)
}
}catch {
print("Ashley Tisdale")
}
}}
Loop over the result from the fetch and append to a string that is then used as value for the label, this goes inside the do{...} where you do the fetch today. Note that I am only using one fetch request here.
itemsName = try context.fetch(fetchRequest)
var mergedScore: String = ""
var mergedAlba: String = ""
for item in itemsName {
if let score = item.value(forKey: "score") as? String {
mergedScore.append(score)
mergedScore.append(" ") //separator
}
if let alba = item.value(forKey: "alba") as? String {
mergedScore.append(alba)
mergedScore.append(" ") //separator
}
}
j1.text = mergedScore
j2.text = mergedAlba
Try this one it's Working for me Swift 4 I think You need to store the value as int which are used as sortDescriptor.
func FetchManagedObjectFromDatabaseForStoreData(Entity :NSEntityDescription) ->
[NSManagedObject]
{
let fetchRequest = NSFetchRequest<NSFetchRequestResult>()
// Add Sort Descriptor
let sortDescriptor = NSSortDescriptor(key: "order", ascending: true)
let sortDescriptor1 = NSSortDescriptor(key: "is_favourite", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor,sortDescriptor1]
// Create Entity Description
fetchRequest.entity = Entity
let result : [NSManagedObject] = []
// Execute Fetch Request
do{
let result = try appDelegate.managedObjectContext.fetch(fetchRequest) as! [NSManagedObject]
if result.count > 0
{
return result
}
else
{
// return result
}
}
catch{
let fetchError = error as NSError
print(fetchError)
}
return result
}
For Fetch Data
// Create Entity Description
let entityDescription = NSEntityDescription.entity(forEntityName: "Your Entity Name Here", in: appDel.managedObjectContext)
let DataObject = FetchManagedObjectFromDatabaseForStoreData(Entity: entityDescription!)
//Convert Array of NSManagedObject into array of [String:AnyObject]
for item in DataObject{
let keys = Array(item.entity.attributesByName.keys)
// Here is your result
print((item.dictionaryWithValues(forKeys: keys) as NSDictionary).value(forKey: "id") as Any) // And so On Whatewer you Fetch
}

UIPickerView with Core Data Swift4

I am trying to re-use a code from swift 7.3. I feel I am very close.
When I try the code on the simulator, the picker view it is empty, but when I try to use it I have only one data show up.
I also try to adapt and other code to load data from core data to it, didn't work.
Any Idea?
Thank you
import CoreData
class ViewController: UIViewController, UIPickerViewDelegate, UIImagePickerControllerDelegate {
#IBOutlet weak var TextField: UITextField!
#IBOutlet weak var stylePicker: UIPickerView!
var styleData = [PilotBase]()
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PilotBase")
do {
styleData = try context.fetch(fetchRequest) as! [PilotBase]
} catch let err as NSError {
print(err.debugDescription)
}
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "PilotBase")
request.returnsObjectsAsFaults = false
do{
let result = try context.fetch(request)
for data in result as! [NSManagedObject]{
print(data.value(forKey: "name") as! String)
}
}catch{
print("Failed")
}
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
return styleData.count
} else {
return 0
}
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if component == 0 {
return styleData[row].name
} else {
return nil
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
print(styleData[row])
stylePicker.selectRow(row, inComponent: 0, animated: true)
TextField.text = styleData[row].name
}
}
Below the code to add data to Core Data.
#IBAction func Save(_ sender: Any) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "PilotBase", in: context)
let newUser = NSManagedObject(entity: entity!, insertInto: context)
newUser.setValue(TextField.text!, forKey: "name")
do{
try context.save()
}catch{
print("failed saving")
}
}

Core Data setting all attributes to nil for first time

I am using Xcode 8 with Swift 3
Core Data setting all the attributes to nil for first time on load with no relationships.
Here is the screenshot.
In Console, i am getting this if App is installed for first time. I have deleted and try to run & install from Xcode number of times. Every time it sets all the values to nil.
"author_id" = nil;
"channel_id" = 0;
"created_at" = nil;
desc = nil;
id = 0;
images = nil;
"is_approved" = 0;
"is_bookmarks" = 0;
"is_like" = 0;
"is_wired" = 0;
"like_count" = 0;
link = nil;
"meta_des" = nil;
"post_id" = 0;
pubDate = nil;
"publisher_id" = 0;
"rss_url" = nil;
"share_cat" = nil;
"share_title" = nil;
"tiny_url" = nil;
title = nil;
"title_tag" = nil;
type = nil;
"updated_at" = nil;
url = nil;
Don't understand whats happening there. Anyone has any idea or hint what am i doing wrong or missing here. And this is only happening for "NewsPost" Entity.
Here is the code where i am adding data to core data.
func addData(entityName: String, entityValues: Any) {
var contextt = NSManagedObjectContext()
if #available(iOS 10.0, *) {
contextt = self.persistentContainer.viewContext
} else {
contextt = self.managedObjectContext
}
/// add data
switch entityName {
case EntityName.NewsEntity:
let singlePost = entityValues as! NSDictionary
let id = singlePost.value(forKey: "id") as! NSNumber
let title = singlePost.value(forKey: "title") as! String
let description = singlePost.value(forKey: "description") as! String
let link = singlePost.value(forKey: "link") as! String
let url = singlePost.value(forKey: "url") as! String
let is_approved = singlePost.value(forKey: "is_approved") as! NSNumber
let meta_des = singlePost.value(forKey: "meta_des") as! String
let title_tag = singlePost.value(forKey: "title_tag") as! String
let share_cat = singlePost.value(forKey: "share_cat") as! String
let author_id = singlePost.value(forKey: "author_id") as! String
let type = singlePost.value(forKey: "type") as! String
let publisher_id = singlePost.value(forKey: "publisher_id") as! NSNumber
let channel_id = singlePost.value(forKey: "channel_id")
let share_title = singlePost.value(forKey: "share_title") as! String
let rss_url = singlePost.value(forKey: "rss_url")
let pubDate = singlePost.value(forKey: "pubDate") as! String
let created_at = singlePost.value(forKey: "created_at") as! String
let updated_at = singlePost.value(forKey: "updated_at") as! String
let like_count = singlePost.value(forKey: "like_count") as! NSNumber
let tiny_url = singlePost.value(forKey: "tiny_url") as! String
let publisher = singlePost.value(forKey: "publisher") as! NSDictionary
let pubName = publisher.value(forKey: "name") as! String
let post = NSEntityDescription.insertNewObject(forEntityName: "NewsPost", into: contextt) as! NewsPost
post.id = Int64(id)
post.title = title
post.desc = description
post.link = link
post.url = url
post.is_approved = Int16(is_approved)
post.meta_des = meta_des
post.title_tag = title_tag
post.share_cat = share_cat
post.author_id = author_id
post.type = type
post.publisher_id = Int64(publisher_id)
post.channel_id = (channel_id as? Int64) ?? 0
post.share_title = share_title
post.rss_url = rss_url as? String ?? ""
post.pubDate = pubDate
post.created_at = created_at
post.updated_at = updated_at
post.like_count = Int64(like_count)
post.tiny_url = tiny_url
let images = singlePost.value(forKey: "images") as! NSArray
do {
let data = try JSONSerialization.data(withJSONObject: images, options: .prettyPrinted)
let string = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
post.images = string as? String
} catch {
}
print("post data -\(post.managedObjectContext)")
break
case EntityName.Categories:
entity.setValue(entityValues[0], forKey: EntityName.Entities.catName)
break
default:
break
}
/// we save our entity
do {
try contextt.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
This is how i am fetching data from Core Data.
func fetchData(entityToFetch: String, completion: #escaping (_ result: [NSManagedObject]?)->()) {
var context = NSManagedObjectContext()
if #available(iOS 10.0, *) {
context = CoreDataStack().persistentContainer.viewContext
} else {
context = DataController().managedObjectContext
}
// Fetching core data
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityToFetch)
request.returnsObjectsAsFaults = false
do {
let results = try context.fetch(request)
print("fetch results- \(results as! [NSManagedObject])")
if results.count > 0 {
completion(results as? [NSManagedObject])
} else {
completion(nil)
}
} catch let error {
completion(nil)
print("fetch error -\(error.localizedDescription)")
}
}
Thank in advance.

How to stop download the database every time the app runs?

Every time the app runs it downloads the data from the server, how can I stop it from download if the data is already in the device?
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
preloadData()
return true
}
func applicationWillResignActive(application: UIApplication) {
}
func applicationDidEnterBackground(application: UIApplication) {
}
func applicationWillEnterForeground(application: UIApplication) {
}
func applicationDidBecomeActive(application: UIApplication) {
}
func applicationWillTerminate(application: UIApplication) {
}
// MARK: - Core Data stack
lazy var applicationDocumentsDirectory: NSURL = {
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
return urls[urls.count-1]
}()
lazy var managedObjectModel: NSManagedObjectModel = {
let modelURL = NSBundle.mainBundle().URLForResource("CoreDataDemo", withExtension: "momd")!
return NSManagedObjectModel(contentsOfURL: modelURL)!
}()
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("CoreDataDemo.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
do {
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil)
} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason
dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
abort()
}
return coordinator
}()
lazy var managedObjectContext: NSManagedObjectContext = {
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
// MARK: - Core Data Saving support
func saveContext () {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
let nserror = error as NSError
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}
}
}
// MARK: - CSV Parser Methods
func parseCSV (contentsOfURL: NSURL, encoding: NSStringEncoding) -> [(name:String, detail:String, price: String)]? {
// Load the CSV file and parse it
let delimiter = ","
var items:[(name:String, detail:String, price: String)]?
do {
let content = try String(contentsOfURL: contentsOfURL, encoding: encoding)
print(content)
items = []
let lines:[String] = content.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()) as [String]
for line in lines {
var values:[String] = []
if line != "" {
// For a line with double quotes
// we use NSScanner to perform the parsing
if line.rangeOfString("\"") != nil {
var textToScan:String = line
var value:NSString?
var textScanner:NSScanner = NSScanner(string: textToScan)
while textScanner.string != "" {
if (textScanner.string as NSString).substringToIndex(1) == "\"" {
textScanner.scanLocation += 1
textScanner.scanUpToString("\"", intoString: &value)
textScanner.scanLocation += 1
} else {
textScanner.scanUpToString(delimiter, intoString: &value)
}
// Store the value into the values array
values.append(value as! String)
// Retrieve the unscanned remainder of the string
if textScanner.scanLocation < textScanner.string.characters.count {
textToScan = (textScanner.string as NSString).substringFromIndex(textScanner.scanLocation + 1)
} else {
textToScan = ""
}
textScanner = NSScanner(string: textToScan)
}
} else {
values = line.componentsSeparatedByString(delimiter)
}
let item = (name: values[0], detail: values[1], price: values[2])
items?.append(item)
}
}
} catch {
print(error)
}
return items
}
func preloadData () {
// Load the data file. For any reasons it can't be loaded, we just return
guard let remoteURL = NSURL(string: "https://drive.google.com/open?id=0B4xB0m95siM2OVRCclRIRXZWZXM/menudata.csv") else {
"https://googledrive.com/host/0ByZhaKOAvtNGTHhXUUpGS3VqZnM/menudata.csv"
return
}
// Remove all the menu items before preloading
removeData()
if let items = parseCSV(remoteURL, encoding: NSUTF8StringEncoding) {
// Preload the menu items
for item in items {
let menuItem = NSEntityDescription.insertNewObjectForEntityForName("MenuItem", inManagedObjectContext: managedObjectContext) as! MenuItem
menuItem.name = item.name
menuItem.detail = item.detail
menuItem.price = (item.price as NSString).doubleValue
do {
try managedObjectContext.save()
} catch {
print(error)
}
}
}
}
func removeData () {
// Remove the existing items
let fetchRequest = NSFetchRequest(entityName: "MenuItem")
do {
let menuItems = try managedObjectContext.executeFetchRequest(fetchRequest) as! [MenuItem]
for menuItem in menuItems {
managedObjectContext.deleteObject(menuItem)
}
} catch {
print(error)
}
}
}
If it's always the exact same data, then you can just try to fetch one entity...
ObjC...
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"MyEntity"];
fetchRequest.fetchLimit = 1;
NSError *error;
NSArray *result = [context executeFetchRequest:fetchRequest error:&error];
if (result == nil) {
// Handle error...
} else if (result.count == 0) {
// You know you do not have any items, so download
}
However, if your server data can change, and it's bulk, then you may want to compute a hash of the data (SHA-1 or similar). You can store the hash from the last bulk data you downloaded, and ask the server for the current hash value.
If the values are different (or if you have no hash value), then get the data from the server.
If it's incremental, you can use the same hash, or just use a timestamp from the server from the last modification to the server data. The client can store that. If they are different, then pull down data since the last timestamp -- (note it does not have to be a timestamp... it could easily just be an incrementing number).
EDIT
I have yet to have any reason to learn swift (though you probably should at least learn to read ObjC since the vast majority of all iOS/OSX code is written in ObjC), so this is just a feeble, uncompiled attempt.
In particular, I'm not sure if "let" creates a constant where the variable binding can't be changed, or if it makes it "const" in a C++ sense, where it can't accept mutating methods, so setting the fetchLimit may or may not work.
let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: "MyEntity")
fetchRequest.fetchLimit = 1
do {
let result = try context.executeFetchRequest(fetchRequest)
// I assume this code only gets executed if there is no error
if result.count == 0 {
// You know you do not have any items, so download
}
} catch let error as NSError {
// Handle error
}

How to write into the CoreData entity at the same index in different view controller (also swift files)

I have created core data using 'NSFetchedResultController' and 'managedObjectContext' in a table view. But in the later view controller, after gathering accelerometer data and conduct calculation, I will get some results that I also want to store in the same row index with the core data I created before.
How can I achieve this? If I create managedObjectContext again, it will create another 'row' of core data in this table.
The code in tableViewController:
'
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
var fetchedResultController: NSFetchedResultsController = NSFetchedResultsController()
override func viewDidLoad() {
super.viewDidLoad()
fetchedResultController = getFetchedResultController()
fetchedResultController.delegate = self
fetchedResultController.performFetch(nil)
}
func getFetchedResultController() -> NSFetchedResultsController {
fetchedResultController = NSFetchedResultsController(fetchRequest: trialFetchRequest(), managedObjectContext: managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
return fetchedResultController
}
func trialFetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "Trials")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)]
return fetchRequest
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
let numberOfSections = fetchedResultController.sections?.count
return numberOfSections!
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let numberOfRowsInSection = fetchedResultController.sections?[section].numberOfObjects
return numberOfRowsInSection!
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let trial = fetchedResultController.objectAtIndexPath(indexPath) as Trials
cell.textLabel?.text = trial.trialName
cell.detailTextLabel?.text = trial.date?.description
return cell
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
let managedObject:NSManagedObject = fetchedResultController.objectAtIndexPath(indexPath) as NSManagedObject
managedObjectContext?.deleteObject(managedObject)
managedObjectContext?.save(nil)
}
func controllerDidChangeContent(controller: NSFetchedResultsController!) {
tableView.reloadData()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "showTrial" {
let indexPath = tableView.indexPathForSelectedRow()
let trial:Trials = fetchedResultController.objectAtIndexPath(indexPath!) as Trials
let trialController:TrialDetailViewController = segue.destinationViewController as TrialDetailViewController
trialController.trial = trial
}
}
'
The code in the createTrial Controller:
' let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "createTrial" {
if theTrialName.text != "" {
createTrial()
} else {
let alertView = UIAlertController(title: "", message: "Trial name couldn't be empty", preferredStyle: .Alert)
alertView.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
presentViewController(alertView, animated: true, completion: nil)
}
}
}
func createTrial() {
let entityDescripition = NSEntityDescription.entityForName("Trials", inManagedObjectContext: managedObjectContext!)
let trial = Trials(entity: entityDescripition!, insertIntoManagedObjectContext: managedObjectContext)
trial.trialName = theTrialName.text
trial.location = theLocation.text
trial.notes = theNotes.text
if theArm.on {
trial.arm = 1
} else {
trial.arm = 0
}
managedObjectContext?.save(nil)
}
'
ps. the view controller I want to get data from is not this following segue, it is around 3 view afterwards. And I have created a string to store the data I need in that view.
I solved the problem by creating the object in the tableViewControllerand use segue twice to transmit it to secondViewControllerand thirdViewController. So it will be at the same index for the whole time. And I can change the content of the core data at both the following view controllers.

Resources