Adding to Managed Object Ordered Set - ios

I have a test app with two entities, folder with a relationship of To Many with another object called List.
In my storyboard I have a tableview controller with the list of created folders. When tapping on a folder I segue to another TableView passing on the selectedFolder which should display the List ordered set saved to the selectedFolder. I have a modal that appears to add a item to the List.
Unfortunately I have not been able to save a List to the selectedFolder ordered set. I receive an error when executing the save function unrecognized selector sent to instance this error is because of the following line:
selectedFolder.list = list.copy() as! NSOrderedSet
I am not sure what I am doing wrong with the save function and was wondering if anyone could help, it would be much appreciated.
Folder Subclass:
class Folder: NSManagedObject {
#NSManaged var title: String
#NSManaged var details: String
#NSManaged var date: NSDate
#NSManaged var list: NSOrderedSet
}
List Subclass
class List: NSManagedObject {
#NSManaged var item: String
#NSManaged var folder: Event
}
Modal View to add List to selected Folder ordered set.
class PopoverViewController: UIViewController {
//selectedFolder passed from segue. Works fine displays title of folder
var selectedFolder: Folder!
#IBOutlet weak var popoverTextField: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
self.popoverTextField.becomeFirstResponder()
// Do any additional setup after loading the view.
}
#IBAction func addListItem(sender: AnyObject) {
//Get the context
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
//get entity details
let entity = NSEntityDescription.entityForName("List", inManagedObjectContext: moc!)
//Create the managed object to be inserted
let list = List(entity: entity!, insertIntoManagedObjectContext: moc!)
// Add Text
list.item = popoverTextField.text
//Insert the new checklist into the folder set
var folder = selectedFolder.list.mutableCopy() as! NSMutableOrderedSet
folder.addObject(list)
selectedFolder.list = list.copy() as! NSOrderedSet
//Error check & Save
var error: NSError?
if moc!.save(&error){
println("Could not save: \(error)")
}
}

var folder = selectedFolder.list.mutableCopy() as! NSMutableOrderedSet
folder.addObject(list)
selectedFolder.list = list.copy() as! NSOrderedSet
The last assignment makes no sense because list is a List object and not an (ordered) set, and btw. this computes a folder variable
which is then ignored.
What you probably meant (and this should work) is
var folder = selectedFolder.list.mutableCopy() as! NSMutableOrderedSet
folder.addObject(list)
selectedFolder.list = folder
As already mentioned in a comment, adding to an ordered relationship
is a bit tricky, but can be done with Key-Value coding as
let mutableChecklist = selectedFolder.mutableOrderedSetValueForKey("list")
mutableChecklist.addObject(list)
(Compare Exception thrown in NSOrderedSet generated accessors and Setting an NSManagedObject relationship in Swift.)
In the case of a one-to-many relationship however, the easiest way
is to set the other direction of the relationship:
list.folder = selectedFolder

Related

Issue with setting the text of a TextField with string from NSManagedObject subclass

Description
I am sure I am completely wrong in my line of thinking here, but do correct me. I am simply trying to reference a string I saved in one of my object subclasses. I am doing this by simply trying to set the text of a textField to the string i saved ( on a button click ).
Code
Below you will find the code for my "CrawlerOne+CoreDataProperties.swift" file
import Foundation
import CoreData
extension CrawlerOne {
#nonobjc class func fetchRequest() -> NSFetchRequest<CrawlerOne> {
return NSFetchRequest<CrawlerOne>(entityName: "CrawlerOne");
}
#NSManaged var crawlerAbrasion: String?
#NSManaged var crawlerDistance: String?
}
Here is the part that i am truly just guessing. This code is the "generate" button that is attempting to set the textField to be the string saved
#IBAction func generate(_ sender: AnyObject) {
let appDel:AppDelegate = (UIApplication.shared().delegate as! AppDelegate)
let context:NSManagedObjectContext = appDel.managedObjectContext
let entity1 = NSEntityDescription.insertNewObject(forEntityName:"CrawlerOne", into:context) as NSManagedObject as! CrawlerOne
answer.text = entity1.crawlerAbrasion
}
Finally, here is the code from where I initially saved the String. Note that these two functions are in difference classes & files
#IBAction func save(_ sender: AnyObject) {
let appDel:AppDelegate = (UIApplication.shared().delegate as! AppDelegate)
let context:NSManagedObjectContext = appDel.managedObjectContext
let entity1 = NSEntityDescription.insertNewObject(forEntityName: "CrawlerOne", into:context) as NSManagedObject as! CrawlerOne
entity1.crawlerDistance = distance.text
}
Conclusion
Again, i am absolutely sure that my line of thinking here is just very off, but this is something that i am being pressured to complete very quickly. Also, there is absolutely no errors/messages in regards to this in the Debug Console. I simply click the button to add text to this Textfield and nothing happens. Thank you in advance! :)
Few things:
the property holding the String isn't identical in the generate and save methods
not sure if you need to cast to NSManagedObject if CrawlerOne is already an NSManagedObject
You are trying to fetch the String by inserting a new object in the managed context, using the result of that operation and therefore setting the label to a text that is nil. You need to create an NSFetchRequest with proper NSPredicates to fetch the original object from your NSManagedContext. You actually have one defined already in your CrawlerOne extension:
class func fetchRequest() -> NSFetchRequest<CrawlerOne>
Now you need to configure it to get the string(s) you are looking for. As mentioned, use NSPredicate for that. There's lots of examples of how to do that, e.g. here.

CoreData - fill array with entity attribute in swift

I'm pretty new to Swift/Xcode and I'm faced with a problem of trying to place CoreData attribute values into an array.
The app is a simple shopping list with an entity with 5 attributes:
entity is "Item"
image Binary data
name String
note String
qty String
status Boolean
The user creates a new shopping item which is displayed in a UITableVIewController (so far I'm fine)
I have to data loading into cells correctly.
I also want to display the total quantity(entity "qty") of items in a label.
I can't seem to put each qty into an array so that I can add them together to display them. There seems to be plenty of resources for Objective C but not much out there for swift.
These are what I've been looking at:
Filling an array with core data attributes
CoreData to Array in Swift
Swift: Fetch CoreData as Array
coredata - fetch one attribute into an array
UITableViewController Code:
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc2 : NSFetchedResultsController = NSFetchedResultsController()
func fetchRequest2() -> NSFetchRequest {
let fetchRequest2 = NSFetchRequest(entityName: "Item")
let sortDescriptor2 = NSSortDescriptor(key: "name",ascending:true)
fetchRequest2.sortDescriptors = [sortDescriptor2]
return fetchRequest2
}
override func viewDidLoad() {
super.viewDidLoad()
//set frc
frc2 = getFRC()
frc2.delegate = self
do {
try frc2.performFetch()
} catch {
print("failed to perform initial fetch request")
return
}
self.tableView.reloadData()
}
Any help would be greatly appreciated.

Update Core Data from a UISwitch inside a TableViewCell

I have a static table that is bound to some Core Data values, I'm not sure how I would use NSFetchedResultsController in this instance, though I have seen discussions about how much more recommended it is.
I grab my Core Data object which is passed via Segue.
I also have a model that is setup to contain questions, with one of the properties containing the Core Data value (this is why I don't think I can use NSFetchedResultsController, as even though my Core Data entity contains some of the values I need, I'm not sure I would need a full data set)
self.surveyQuestion.append(SurveyQuestion(question: "Does the customer have a 'Proof of ownership'?", answer: coreDataEntity.isProofOfOwnership as Bool))
The questions are Survey related such as "Is your property X?" with a UiSwitch which is mapped to a Core Data value:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Configure the cell...
let cell : SurveyQuestionTableViewCell = tableView.dequeueReusableCellWithIdentifier("SurveyQuestionCell") as! SurveyQuestionTableViewCell
cell.lblQuestion.textColor = UIColor.grayColor()
let surveyQuestion = self.surveyQuestion[indexPath.row]
cell.lblQuestion.text = surveyQuestion.question
cell.toggQuestion.on = surveyQuestion.answer
if cell.toggQuestion.on {
cell.lblQuestion.textColor = UIColor.blackColor()
cell.accessoryType = .DetailDisclosureButton
}
return cell
}
Now, when I tap on the UISwitch I need it to update the Core Data value, and reload the table, its hooked up to a CustomTableViewCell like so:
*edit - Nearly got this thing working! heres my UITableViewCell class
class SurveyQuestionTableViewCell: UITableViewCell {
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
#IBOutlet weak var lblQuestion: UILabel!
#IBOutlet weak var toggQuestion: UISwitch!
var surveyQuestionReference : SurveyQuestion?
var tableViewReference : UITableView?
#IBAction func toggledQuestion(sender: AnyObject) {
let tempContext: NSManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
tempContext.parentContext = self.managedObjectContext
tempContext.performBlock({
let entityName = "CoreDataEntity"
let request = NSFetchRequest(entityName: entityName)
request.predicate = NSPredicate(format: "id = %#", self.surveyQuestionReference!.id)
do {
let results = try tempContext.executeFetchRequest(request) as? [NSManagedObject]
if results!.count > 0{
if let moc = self.managedObjectContext{
moc.performBlockAndWait({
for result in results!{
result.setValue(self.toggQuestion.on, forKey: (self.surveyQuestionReference?.property)!)
}
})
}
}
do {
try tempContext.save()
//completion(finished: true)
} catch let error {
print(error)
}
}catch{
print("error")
}
})
print(sender)
dispatch_async(dispatch_get_main_queue()) {
self.tableViewReference!.reloadData()
}
}
I can obviously access the bit where the toggle is triggered, but this class doesn't know anything about the Core Data bit, I was thinking about using notifications but that just seems kind of messy...
when you create your cell, pass in a reference to the coredata object, and the tableView itself and store them as attributes of SurveyQuestionTableViewCell, then you can do everything you need to in setSelected()
in your custom cell class, add an attribute for the question
class SurveyQuestionTableViewCell: UITableViewCell {
#IBOutlet weak var lblQuestion: UILabel!
#IBOutlet weak var toggQuestion: UISwitch!
var surveyQuestionReference : SurveyQuestionType
vat tableViewReference : UITableView
...
and then in cellForRowAtIndexPath after you create the cell
cell.surveyQuestionReference = surveyQuestion
cell.tableViewReference = tableView
where SurveyQuestionType is whatever you have previously defined
in setSelected, you can use those stored attributes
surveyQuestionReference = self.toggQuestion.on
tableViewReference.reloadData()
Here's another option, using a shared Instance
import Foundation
import MapKit
import CoreData
class DataModelInstance : NSObject, NSCoding
{
var appDelegate : AppDelegate?
var managedContext : NSManagedObjectContext?
var persistentStoreCoordinator : NSPersistentStoreCoordinator?
// plus whatever else you need
class var sharedInstance : DataModelInstance
{
struct Singleton
{
static let instance = DataModelInstance()
}
return Singleton.instance
}
and then in any class which needs access to this data model
var dataModel = DataModelInstance.sharedInstance
I know there are those who just won't ever use singletons, but it can be a much more elegant solution to making these attributes available where they are needed
With a shared data model, you can simply move all of your data attributes out of the class they are currently in, and reference them through the data model - then if you have the same data model in your custom cell class, you can do whatever you can do in the main view. To keep your GUI and processing logic separate, you can put everything in the data model
dataModel.refreshTable()
and then define a function in the data model that takes care of your table view - you could save all current edits to the data, and reload, without having to put any of that logic in individual cell classes
for updating any record in core data try to use this code:
let managedObjectContext:NSManagedObjectContext=(UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext!
let req=NSFetchRequest(entityName: "Entity Name")
req.returnsObjectsAsFaults=false
let result:NSArray=try! managedObjectContext.executeFetchRequest(req)
if result.count>0{
let res=result[Int(indexPath.row!]as! NSManagedObject
res.setValue("The Value", forKey: "Key Name")
do {
try managedObjectContext.save()
} catch _ { print("Update Unsuccessful") }
You must use [unowned self] in within the closure. See Apple's docs. This is how it's done. See also CoreDataKit, a 28-star github repo Core Data stack. It's available on cocoapods and honestly, why not just drop something like this into your app and not worry about "unowned selves" and other philosophical brain twisters, eh?
if let moc = self.managedObjectContext{
moc.performBlockAndWait({
/* In here we are in a closure (Swift version of a block), so "self" is problematic. Use unowned self instead (in objective c you'd have to do weak self). */
[unowned self] in
for result in results!{
result.setValue(self.toggQuestion.on,
forKey: (self.surveyQuestionReference?.property)!)
}
})
}

Using 'didMoveCellFromIndexPathToIndexPathBlock' with Core Data

I have a custom UITableView that takes care of animations for rearranging cells and works perfectly with standard test array which doesn't use core data.
When trying with an application that uses core data with two entities 'Folder' and 'Item' with a To Many relationships I receive an error.
[(Item)] does not have a member called exchangeObjectAtIndex
for;
tableView.didMoveCellFromIndexPathToIndexPathBlock = {(fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) -> Void in
let delegate:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = delegate.managedObjectContext!
self.itemsArray.exchangeObjectAtIndex(toIndexPath.row, withObjectAtIndex: fromIndexPath.row)
var error: NSError? = nil
if !context.save(&error) { abort() }
}
This is because:
The NSSet, NSMutableSet, and NSCountedSet classes declare the
programmatic interface to an unordered collection of objects.
So I tried converting the NSSet to an NSMutableArray to manage the objects order.
func itemsMutableArray() -> NSMutableArray {
return NSMutableArray(array: (item.allObjects as! [Item]).sorted{ $0.date.compare($1.date) == NSComparisonResult.OrderedAscending } )
}
But then I get the following error in my tableview; this was because the mutable array is an AnyObject so swift doesn't believe it has a title property.
cell.textLabel?.text = folderMutableArray[indexPath.row].title
So then I go back to where I started. I am just trying to create a simple list and rearrange the order of objects.
Here is my Folder Class:
class Folder: NSManagedObject {
#NSManaged var date: NSDate
#NSManaged var title: String
#NSManaged var item: NSSet
/// Method 1 - Converting to a NSMutableArray
// func itemsMutableArray() -> NSMutableArray {
// return NSMutableArray(array: (item.allObjects as! [Item]).sorted{ $0.date.compare($1.date) == NSComparisonResult.OrderedAscending } )
// }
// Method 2 - creating an array of type Item
func itemArray() -> [Item] {
let sortByDate = NSSortDescriptor(key: "Date", ascending: true)
return item.sortedArrayUsingDescriptors([sortByDate]) as! [Item]
}
}
Productivity apps do this all the time so I know it's possible but unsure how, Does anyone know where I am going wrong or have any ideas or suggestions ?
This is very easy to achieve. You need to add a property called index to your Item. Set it to an integer type and whenever you add a new Item set this value to the index under which you want an item to appear. This property should be added both in Core Data Model and in your Item's class as NSManaged property.
Next you need to add a transient property to your Folder class called arrayOfItems (you can rename this of course to whatever you want). In Core Data Model set it to Transient and Transformable.
In your Folder class do the following:
class Folder: NSManagedObject {
#NSManaged var date: NSDate
#NSManaged var title: String
#NSManaged var item: NSSet
#NSManaged var arrayOfItems: [Items]
override func awakeFromFetch() {
super.awakeFromFetch()
self.regenerateItems()
}
func regenerateItems() {
let desc = NSSortDescriptor(key: "index", ascending: true)
if let array = item.sortedArrayUsingDescriptors([desc]) as? [Item] {
self.arrayOfItems = array
}
}
}
Now whenever you fetch any instance of Folder you will get a correct sorted and mutable array of Items. There are only two other cases you need to consider.
And they result from the fact that awakeFromFetch is only getting called when you fetch your data from Core Data. So, you have to consider other scenarios.
Adding new Item
When you add new Item you need to either manually append the new Item to arrayOfItems or you need to call regenerateItems() once you are finished adding the new Item. For example, lets assume that somewhere in your code you create your initial data:
var folder = NSEntityDescription.insertNewObjectForEntityForName("Folder", inManagedObjectContext: self.managedObjectContext!) as! Folder
var firstItem = NSEntityDescription.insertNewObjectForEntityForName("Item", inManagedObjectContext: self.managedObjectContext!) as! Item
// assuming that you have an inverse relationship from Item to Folder
// line below will assign it and will add your firstItem to Folder's
// NSSet of items
firstItem.folder = folder
// at this point your firstItem is added to your Folder but
// arrayOfItems is empty. So, you should call
folder.arrayOfItems = [firstItem]
// or you can call folder.regenerateItems()
Code above refers to the situation when you create your initial data. If somewhere in your code you add a new Item to the folder which already has some Items you have the following:
var newItem = NSEntityDescription.insertNewObjectForEntityForName("Item", inManagedObjectContext: self.managedObjectContext!) as! Item
// assuming that you have an inverse relationship from Item to Folder
// line below will assign it and will add your newItem to Folder's
// NSSet of items
newItem.folder = someExistingFolder
// at this point your newItem is added to your Folder but
// arrayOfItems is not yep updated and does not include newItem
// so you should either call
folder.arrayOfItems.append(newItem)
// or you can call folder.regenerateItems()
Rearranging Items
Also, when you move Items in your table view you will need to change their index and order in the arrayOfItems. The easiest way to achieve this would probably be to change the order of items in the arrayOfItems and then to iterate through this array assigning correct new indexes to all items within it:
func tableView(tableView: UITableView, moveRowAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
let itemThatMoved = currentFolder.arrayOfItems[sourceIndexPath.row]
currentFolder.arrayOfItems.removeAtIndex(sourceIndexPath.row)
currentFolder.arrayOfItems.insert(itemThatMoved, atIndex:destinationIndexPath.row )
var currentIndex = 0
for item in currentFolder.arrayOfItems {
item.index=currentIndex
currentIndex++
}
}
Let me know if this helps or if you have any other questions.
P.S. I strongly encourage you to change the name of your relationship from item to items. Plural name items is much more logical for NSSet.

Implementing a UISearchController in Swift Core Data Project

I'm presently taking an iOS development course. As part of an assignment, I'm tasked with creating a UISearchController in a note tracking project using Core Data in Swift.
Every example I've found is in Objective-C or is filtering a static array. Apple's "sample" code, updated in December 2014 doesn't compile in Xcode 6.3.
To add a UISearchController, I've got 3 primary tasks to do:
1) Create a view controller to present search results. I'm using a TableViewController.
2) Create a UISearchController, and pass it my search results view controller.
What's "stumping" me is now to get a hold of the objects in the managedObjectsContext. Prior to attempting to add a UISearchController, my app works fine. I can add, edit, and delete items. I'm using the "canned" Core Data code in Xcode 6.3 with the stack in AppDelegate.
class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate
var searchController: UISearchController? = nil
func addSearchBar() {
var resultsController = SearchResultsTableViewController()
resultsController.notes = // stumped what to call the notes. Trying to call an array from the Notes class below
resultsController.delegate = self
searchController = UISearchController(searchResultsController: resultsController)
searchController!.searchResultsUpdater = resultsController
searchController!.searchBar.frame = CGRect(
x: searchController!.searchBar.frame.origin.x,
y: searchController!.searchBar.frame.origin.y, width: searchController!.searchBar.frame.size.width, height: 44.0)
tableView.tableHeaderView = searchController!.searchBar
self.definesPresentationContext = true
}
3) The UISearchController will notify its searchResultsUpdater (a class that conforms to UISearchResultsUpdating) when the search text changes. I'm using my search results view controller implement this protocol so I can update the filtered results.
Below is my Note: NSManagedObject class:
import Foundation
import CoreData
class Note: NSManagedObject {
#NSManaged var dateCreated: NSDate
#NSManaged var dateEdited: NSDate
#NSManaged var noteTitle: String
#NSManaged var noteBody: String
// TODO: adding this to make it easier to handle names
class func notes() -> NSArray {
let whereMyNotesAreStored = // Need syntax for where my MyManagedObjectContext is located
let dataArray = NSArray(contentsOfFile: whereMyNotesAreStored!)
var notesArray = NSMutableArray()
for dictionary in dataArray {
var note = Note()
note.noteTitle = dictionary["noteTitle"] as! String
note.noteBody = dictionary["noteBody"] as! String
note.dateCreated = dictionary["dateCreated"] as! String
note.dateEdited = dictionary["dateEdited"] as! String
notesArray.addObject(note)
}
return NSArray(array: notesArray as! [AnyObject])
}
}
There are two approaches to setting the context:
Calling back to the App Delegate:, like this
let appDelegate : AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context = appDelegate.managedObjectContext!
or passing the context forward from the App Delegate to the Master View Controller, which then passes it on to any subsequent view controllers, etc. Each view controller will need a property defined for the context; when a new VC is instantiated, the context is set before the VC is presented/pushed, eg:
class CustomViewController : UIViewController {
var managedObjectContext : NSManagedObjectContext
...
and, when loading a new view controller,
let newVC = CustomViewController()
newVC.managedObjectContext = self.managedObjectContext
...
To access the objects, use either a NSFetchRequest or NSFetchedResultsController to create an array of results, which you can then pass to the resultsController. eg. for a fetch request:
let fetch = NSFetchRequest(entityName: "Notes")
var error : NSError? = nil
let fetchedResults = managedObjectContext?.executeFetchRequest(fetch, error: &error)
(Your notes() function is on the wrong track - you would not use NSArray(contentsOfFile:) to access CoreData objects. Also, you must use the designated initialiser for NSManagedObject subclasses: so not var note = Notes() but var note = Notes(entity: NSEntityDescription, insertIntoManagedObjectContext: NSManagedObjectContext?)

Resources