Aim :
To be able to display the days selected and the time picked by the user in the same row of the table view. The time should appear at the top and the days selected should appear at the bottom, both in the same row, just like an alarm clock.
Work :
This is the relationship I've got setup :
and this is how I save the days that are selected from a UITable and the time from a UIDatepicker when the save button is tapped :
#IBAction func saveButnTapped(_ sender: AnyObject)
{
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext //creates an object of a property in AppDelegate.swift so we can access it
let bob = Bob(context: context)
//save the time from UIDatePicker to core data
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
bob.timeToPing = dateFormatter.string(from: timePicked.date)
// save the days selected to core data
for weekday in filteredWeekdays
{
var day = Days(context: context) //create new Days object
day.daysSelected = weekday as NSObject? //append selected weekday
bob.addToTimeAndDaysLink(day) //for every loop add day object to bob object
}
//Save the data to core data
(UIApplication.shared.delegate as! AppDelegate).saveContext()
//after saving data, show the first view controller
navigationController!.popViewController(animated: true)
}
Now that the data is once saved, I get the data :
func getData()
{
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do
{
bobs = try context.fetch(Bob.fetchRequest())
}
catch
{
print("Fetching failed")
}
}
Attempt to get the days selected :
I tried to follow this, the below comments and a formerly deleted answer to this question to do this :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = UITableViewCell()
let bob = bobs[indexPath.row]
cell.textLabel?.text = bob.timeToPing?.description
// retrieve the days that are selected
var daysArray: [Days] = []
daysArray = bob.timeAndDaysLink?.allObjects as! [Days]
for days in daysArray
{
print (days.daysSelected?.description)
cell.textLabel?.text = days.daysSelected! as! String
}
return cell
}
EDIT :
print(daysArray) gives this :
[<Days: 0x6080000a5880> (entity: Days; id: 0xd000000000040000 <x-coredata://30B28771-0569-41D3-8BFB-D2E07A261BF4/Days/p1> ; data: <fault>)]
print(daysArray[0]) gives this :
<Days: 0x6080000a5880> (entity: Days; id: 0xd000000000040000 <x-coredata://30B28771-0569-41D3-8BFB-D2E07A261BF4/Days/p1> ; data: <fault>)
How to save days
let weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
var filteredWeekdays: [String] = []
#NSManaged public var daysSelectedbyUser: NSSet
And then
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
selectedWeekdays()
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath)
{
selectedWeekdays()
}
func selectedWeekdays()
{
if let selectedRows = tableView.indexPathsForSelectedRows
{
let rows = selectedRows.filter {$0.section == 0}.map{ $0.row}
filteredWeekdays = rows.map{ weekdays[$0] }
print(filteredWeekdays)
}
}
Many thanks!
OK based on your latest comment that the crash occur on this line:
cell.textLabel?.text = days.value(forKey: "daySelected") as! String
It's clearly pointing to the typo you've made in key name. You have: daySelected and should be (based on your core data model) daysSelected, but nevertheless it's not very good approach to use values for your core data entity and also force type like that. To make it better I suggest replacing this line with:
cell.textLabel?.text = days.daysSelected!
This should be already a String since this is a String in CoreData. In case it's optional (should be an optional), you shouldn't force it. I will assume that whenever data will be not there you will just display empty cell, so even better it will be:
cell.textLabel?.text = days.daysSelected ?? ""
This will produce empty string to text, whenever (for some reason) data will be not there.
EDIT
So for additional piece of code you put in your question:
In your CoreData field daysSelected is type of String?, right?
Then you assign timeAndDateLink to NSSet<String>, right? But expected value here should be NSSet<Days>.
So let's edit your input code a bit ( i will put comment on every line):
let bob = Bob(context: context) /* create new Bob object */
for weekday in filteredWeekdays {
var day = Days(context: context) /* create new Days object */
day.daysSelected = weekday /* append selected weekday */
bob.addToTimeAndDaysLink(day) /* for every loop add day object to bob object */
}
I hope everything is clear in above example. You may have a problem with a compiler in that case, because if you choose generate class for entities you will endup with two func with the same name but different parameter (in Swift this should be two different functions, but Xcode sometimes pointing to the wrong one). If you hit that problem try:
let bob = Bob(context: context) /* create new Bob object */
var output: NSMutableSet<Days> = NSMutableSet()
for weekday in filteredWeekdays {
var day = Days(context: context) /* create new Days object */
day.daysSelected = weekday /* append selected weekday */
output.add(day)
}
bob.addToTimeAndDaysLink(output) /* this will assign output set to bob object */
You should also rename your Days entity to Day to avoid future confusion that we have right now, days as array will only be in relation from other entities to this not entity itself.
I don't know why no one uses FetchedResultsController, which is made for fetching NSManagedObjects into tableView, but it doesn't matter I guess...
Problem in this question is that you didn't post here your NSManagedObject class for the variable, so I cannot see which type you set there (Should be Transformable in CoreData model and [String] in NSManagedObject class...)
Ignoring all force unwraps and force casting and that mess (which you should pretty damn well fix as first, then it won't crash at least but just don't display any data...)
Days selected by user is NSSet, which it sure shouldn't be.
Please provide you NSManagedObjectClass in here so I can edit this answer and solve your problem...
Related
Every time different users post something (let's say a color) I get the color they posted, the postID, their userId, the date in secs, and how many times that post was viewed.
A different user can look through a tableView and view different cells with every color that every user posted .
Every time that user who is looking taps didSelectRow to view a detail view of the color I run a Firebase TransactionBlock that increases a views count property to show how many times that particular color/cell was tapped.
For eg if the user scrolls through a tableView and see's a blueCell, a label will be on it that says views: 10 (meaning it was viewed 10 times). If that user presses that blueCell again then the views count will go show views: 11.
The problem is if that user presses that cell repeatedly then they can increase the count on that views label in matter of seconds.
How can I keep track of every object/cell that the user taps and put a timer on it so that they can't update the views count for that particular object for possibly another hour or so? I have the date in secs and postId which are unique to each object.
Basically if the user presses the blueCell at 12pm the views count for the object associated with that particular cell will go up to 11 but if they press it again anytime in between 12pm - 1pm it won't go up. After 1pm if they press it again it the views count for that object will go up to 12?
The model object and the properties I can use to identify each color object:
class ColorClass{
var color: String?
var postID: String?
var userId: String?
var date: NSNumber?
var views: NSNumber? // keeps track of how many the post was viewed
}
TableView's didSelectRow:
// the current user who is pressing the cell
let currentUserID = Auth.auth().currentUser?.uid
var colors = [ColorClass]() // 500 model objects
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return colors.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ColorsCell", for: indexPath) as! ColorsCell
cell.viewsLabel.text = colors[indexPath.row].views // I separately convert this from a NSNumber to a String
cell.colorLabel.text = colors[indexPath.row].color
return cell
}
// pressing the cell will increase the count on the object's views property
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let indexPath = tableView.indexPathForSelectedRow else { return }
// the userId on the object of the cell that was pressed
guard let userID = colors[indexPath.row].userId else { return }
guard let postID = colors[indexPath.row].postId else { return }
// make sure the current user can't update the views on their own post
if currentUserID != userID{
let viewsRef = databaseRef?.child(userID).child(postID).child("views")
viewsRef?.runTransactionBlock({
(currentData: MutableData) -> TransactionResult in
let newValue: Int
guard let existingValue = (currentData.value as? NSNumber)?.intValue else {
return TransactionResult.abort()
}
newValue = existingValue + 1
currentData.value = NSNumber(value: newValue)
return TransactionResult.success(withValue: currentData)
}, andCompletionBlock: {
(error, completion, snap) in
print(snap as Any)
if !completion{
print("The value wasn't able to update")
print(error?.localizedDescription as Any)
}else{
print("The value updated")
}
})
}
}
Just an idea.
I thought about creating another object that would have a currentUserID, postID, and tappedTime properties. Then I would create a singleton. Every time a cell is pressed I’d pass the data into the object then send the object over to an array in the singleton. In there I’d have a currentTime property. First I’d check if the postID is in the array and if so I’d compare the tappedTime to the currentTime + 1 hour to decide if the views count should get increased. I’d have a dispatch asynch timer and after 1 hour it would automatically get purged from the array. I’m not sure how practical it is though.
You could create a typealias consisting of whatever the object is you're populating your cells with and a Date at the top of your view controller, like so:
typealias ColorLastSelected = (colorClass: ColorClass, timeSelected: Date)
Then, create an array to store the ColorLastSelected objects.
var selectedColors: [ColorLastSelected] = []
From there, in didSelectRow, you could do a guard statement to check if an object is contained within the selectedColors array. If not, then do whatever it is you've got to do and at the end, initialize a ColorLastSelected object and append it to the selectedColors array.
In terms of keeping the selectedColors up to date, you could run an update method on a repeating timer to remove ColorLastSelecteds that are over 1 hour old. Alternatively, you could just filter the selectedColors array before the guard statement to remove stuff that's over an hour old. If you're going to be jumping around between view controllers, you may need to create a singleton that "stays alive" or you could persist the selectedColors array somewhere
The idea I had at the bottom of the question worked.
I basically made a ViewsTrackingObject with a property specifically for the postId
I then made a singleton that adds the viewsTrackingObject to an array, checks to see if its in the array, if not add it to the array, then remove it from the array after xxx secs.
For this example I set it to 15 secs inside step 9: .now() + 15 but if I wanted it for an hour I would change it to .now() + 3600.
I find it easier to explain things in steps. There are 0 - 21 steps. I listed the steps as commented out code above each corresponding piece of code starting at the top of the Tracker class with step 0 and it ends the bottom of didSelectRow with step 21
ViewsTrackingObject:
class ViewsTrackingObject{
var postId: String?
}
Singleton Class:
class Tracker{
static let sharedInstance = Tracker()
var viewsObjects = [ViewsTrackingObject]()
var updateCount = false // 0. need to access this inside didSelectRow (step 17 )to find out wether or not to update the number of views. This would set to true in step 3 below
func checkForObjectInArray(object: ViewsTrackingObject){
// 1. check to see if the object is in the array. If it is return true if not return false. Use dot notation to compare the postId on the viewsTrackingObject vs what's inside the array to find out if it exists
let boolVal = viewsObjects.contains(where: {$0.postId == object.postId})
// 2. if the object is NOT inside the array then append to the array and then add it to the function that will remove it from the array in whatever secs you specify from the moment it's added. I specified 15 secs
if !boolVal{
updateCount = true // 3. change this to true which means in didSelectRow in step 18 return TransactionResult.success(withValue: currentData) will run
viewsObjects.append(object) // 4. add it to the above array property
removeObjectFromArray(object) // 5. will remove the viewsTrackingObject passed into the object parameter above in 15 secs from now. Look at step 9
}
}
// 6. this is called above when an object is appended to the array
func removeObjectFromArray(_ object: ViewsTrackingObject){
// 7. even though the object should definitely be inside the array double check. If it's in there return true if not return false
let boolVal = viewsObjects.contains(where: {$0.postId == object.postId})
// 8. if the object is in the array which mean the boolVal is true then proceed to step 9
if boolVal{
// 9. Fire off in 15 secs from now
DispatchQueue.main.asyncAfter(deadline: .now() + 15) {
// 10. find the index of the viewsTrackingObject inside the array
if let index = self.views.index(where: {$0.postId == viewsModel.postId}){
// 11. remove the viewsTrackingObject at the corresponding index from the array
self.viewsObjects.remove(at: index)
print("++++SUCCESS OBJECT REMOVED++++") // in 15 secs these print statements will print to the console
print("----viewsObjects count: \(views.count)")
print("....viewsObjects items: \(views.description)")
}
}
}
}
}
The class that contains the tableView. Declare a property for the Tracker's sharedInstance so everything runs through the Singleton class
// 12. This is declared as a class property and it's used in didSelectRow. Its the Singleton Class
let tracker = Tracker.sharedInstance
let currentUserID = Auth.auth().currentUser?.uid // the current user who is pressing the cell
var colors = [ColorClass]() // 500 model objects
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let indexPath = tableView.indexPathForSelectedRow else { return }
// 14. Get the postId of the colorObject corresponding to the tapped cell
guard let postID = colors[indexPath.row].postId else { return }
guard let userID = colors[indexPath.row].userId else { return } // the userId on the object of the cell that was pressed. This is used as a child in the databaseRef below to update the user's view's property
// make sure the current user can't update the views on their own post
if currentUserID != userID{
// 15. Create a ViewsTrackingObject and set it's postID property to the same postId property from step 14
let viewsTrackingObject = ViewsTrackingObject()
viewsTrackingObject.postId = postID
// 16. using the tracker's shared instance, call the method to find out if the object is currently inside the Singleton's array
tracker.checkForObjectInArray(object: viewsTrackingObject)
let viewsRef = databaseRef?.child(userID).child(postID).child("views")
viewsRef?.runTransactionBlock({
(currentData: MutableData) -> TransactionResult in
let newValue: Int
guard let existingValue = (currentData.value as? NSNumber)?.intValue else {
return TransactionResult.abort()
}
newValue = existingValue + 1
currentData.value = NSNumber(value: newValue)
// 17. check to see if the singleton's updateCount property was set to true in step 3. If is true then proceed to step 18
if self.tracker.updateCount{
// 18. reset singleton's updateCount property back false since it was set to true in step 3
self.tracker.updateCount = false
print("*****Views Updated")
return TransactionResult.success(withValue: currentData)
}
// 19. if the singleton's updateCount property was false to begin with then the views won't get updated in firebase because the transaction will get aborted
print("=====Views NOT Updated")
return TransactionResult.abort()
}, andCompletionBlock: {
(error, completion, snap) in
print(snap as Any)
if !completion{
// 20. If something went wrong reset singleton's updateCount property back false
self.tracker.updateCount = false
print("The value wasn't able to update")
print(error?.localizedDescription as Any)
}else{
// 21. it's unnecessary but to be on the safe side
self.tracker.updateCount = false
print("The value updated")
}
})
}
}
Please forgive if this is a duplicate question. I did search as thoroughly as I could, but none of the other questions that I've seen regarding core data in a UITableView seem to quite fit what I am trying to do.
Basically, I have two "tables" in my Core Data:
cstProjects and plProjects
Both tables have attributes called "propertyOwner" and "propertyID" (among others), that are set as Strings.
Next, in a ViewController, I have a table view instance, with the style set to Subtitle, and the cell identifier set to "projectCell".
Because I have two tables in my core data, I initialize the table to having two sections. I would like the first section to show all of the projects in the cstProjects table, and the second section to show all of the projects in the plProjects table.
Here is my ViewController code so far. I've put comments in, to explain it as best as I can. The functions that I have created might be a bit overkill to figure out how many "rows" each section should have, but I didn't know of a simpler way of going about it.
In the code below, where you see the double question marks "??" is where I am not sure what to put in order to show the current "row" of the type of project that we're currently iterating through.
Ultimately though, if I had to simplify my question down as much as possible, I just need to know how to show the rows of a core data table in a UITableView.
import UIKit
import CoreData
class LoadProjectViewController: UIViewController, UITableViewDataSource, NSFetchedResultsControllerDelegate {
let projectSectionCount: Int = 2
let cstSection: Int = 0
let plSection: Int = 1
// Implement UITableViewDataSource methods
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return projectSectionCount
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch(section) {
case cstSection:
return getCSTProjects()
case plSection:
return getPLProjects()
default:
return 0
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("projectCell")! as UITableViewCell
switch (indexPath.section) {
case cstSection:
//this is where I need to set the property owner for the CST Project
cell.textLabel!.text = ??
//this is where I need to set the project ID for the CST Project
cell.detailTextLabel!.text = ??
case plSection:
//this is where I need to set the property owner for the PL Project
cell.textLabel!.text = ??
//this is where I need to set the project ID for the PL Project
cell.detailTextLabel!.text = ??
default:
cell.textLabel!.text = "Unknown"
}
return cell
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch section {
case cstSection:
return "Standing Timber Projects"
case plSection:
return "Purchased Logs"
default:
return "Unknown"
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func getCSTProjects() -> Int {
let appDel:AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
let context: NSManagedObjectContext = appDel.managedObjectContext
let request = NSFetchRequest(entityName: "CSTProjects")
var results = [AnyObject]()
request.returnsObjectsAsFaults = false
do {
results = try context.executeFetchRequest(request)
} catch let error {
print("There was an error loading the data. \(error)")
}
if (results.count > 0) {
return results.count
} else {
return 0
}
}
func getPLProjects() -> Int {
let appDel:AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
let context: NSManagedObjectContext = appDel.managedObjectContext
let request = NSFetchRequest(entityName: "PLProjects")
var results = [AnyObject]()
request.returnsObjectsAsFaults = false
do {
results = try context.executeFetchRequest(request)
} catch let error {
print("There was an error loading the data. \(error)")
}
if (results.count > 0) {
return results.count
} else {
return 0
}
}
}
The first thing you need to do in that method is look up which instance you want to display. To do that, you should modify your getCSTProjects and getPLProjects methods to save the results of the fetch request instead of throwing them away. You'd do that by adding new attributes to the view controllers to hold the arrays. On a related note, you really do not want to call those methods from tableView(tableView:, numberOfRowsInSection:), because that method can get called a lot. Doing fetches there pretty much guarantees poor performance.
To get a specific instance, look it up in one of the arrays you now have, using the indexPath argument to get the array index.
Once you have that specific instance, you need to look up values of the attributes you have declared on those Core Data entity types. What to put there depends on how your CSTProjects and PLProjects entities are defined and on whether you have created subclasses of NSManagedObject for them.
For example if your CSTProjects entity has a string attribute called name that you want to use as the title, and you do not have an NSManagedObject subclass, you could use this:
cell.textLabel!.text = currentInstance.value(forKey: "name") as? String
If you do have an NSManagedObject subclass (and it's always a good idea), this would be
cell.textLabel!.text = currentInstance.name
Rather than try to merge two entities (or tables) into a single fetchedResultsController, it would be easier to bring the data into a single table and access that.
If the tables are sufficiently similar, you could do this in a single table with a type code to differentiate the entries where you need to, for example using a filtered view to make the tables look like one type or the other in other parts of your code. This would mean making any fields limited to one type optional so the other could ignore them. Then the frc could use the code field as a section break.
Alternatively, if the tables are sufficiently different you could add a third table, say Projects, which had optional one-to-one links to the other two tables and drive the frc off of that third table using the links to populate fields in your tableView in the configure cell delegate method.
Besides making it easier to build the current frc structure, either of these approaches would make adding a third or fourth type much easier if your requirements expand in the future, and would avoid the complications that inevitably follow from bending an API to fit a use it wasn't designed for.
Im making a simple planner app which sends notifications to users at specific times that events occur.
I have set up a table to store the data and I am storing individual values inside of an array.
I am encountering a problem outputting the NSDates that I have stored inside of my array.
import UIKit
extension NSDate {
convenience init(dateString:String, format:String="h-mm a") {
let formatter = NSDateFormatter()
formatter.timeZone = NSTimeZone.defaultTimeZone()
formatter.dateFormat = format
let d = formatter.dateFromString(dateString)
self.init(timeInterval:0, sinceDate:d!)
}
class MedicineTableViewController: UITableViewController {
//MARK Properties
var medicines = [Medicine]()
override func viewDidLoad() {
super.viewDidLoad()
loadSampleMedicine()
}
func loadSampleMedicine() {
let medicine1 = Medicine(name: "Inhaler", time1: NSDate(dateString: "08:00 a"), time2: NSDate(dateString: "10:00 a"), time3: NSDate(dateString: "02:00 p"), time4: NSDate(dateString: "06:00 p"), time5: NSDate(dateString: "10:00 p"))
medicines.append(medicine1!)
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return medicines.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "MedicineTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! MedicineTableViewCell
let medicine = medicines[indexPath.row]
cell.nameLabel.text = medicine.name
cell.takeAt.text = "Take At:"
cell.time1Label.text = medicine.time1
cell.time2Label.text = medicine.time2
cell.time3Label.text = medicine.time3
cell.time4Label.text = medicine.time4
cell.time5Label.text = medicine.time5
return cell
}
This returns the error "Cannot assign a value of 'NSDate' to a value of type String?"
Is there a way to convert these NSDates into strings?
I have come up with some other possible solution but it involves reworking the whole application so I'd prefer to avoid them if possible.
My possible solution is to rework the data that the user inputs to be a pickerView which has 3 columns one cycling the numbers 01 through to 12, the second 00 to 59 and the third am and pm. and then take the overall string produced and store it in the array. This would allow me to easily print it out since it is just a stored string. Then when I come to the stage at which I am making the notification system I could use the "dateString" function to convert from strings to dates and then program my notifications from that.
So overall I would like to know if I'm able to just print out the NSDates stored in my array or if not if my possible solution would work?
Thanks.
You can use NSDateFormatter. There is a function called stringFromDate. Here is an example.
var date = NSDate() //Or whatever your date is
var stringDate = NSDateFormatter().stringFromDate(date)
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.
In my RealmSwift (0.92.3) under Xcode6.3, how would I
// the Realm Object Definition
import RealmSwift
class NameEntry: Object {
dynamic var player = ""
dynamic var gameCompleted = false
dynamic var nrOfFinishedGames = 0
dynamic var date = NSDate()
}
The current tableView finds the number of objects (i.e. currently all objects) like follows:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let cnt = RLM_array?.objects(NameEntry).count {
return Int(cnt)
}
else {
return 0
}
}
First question: How would I find the number of objects that have a date-entry after, let's say, the date of 15.06.2014 ?? (i.e. date-query above a particular date from a RealmSwift-Object - how does that work ?). Or in other words, how would the above method find the number of objects with the needed date-range ??
The successful filling of all Realm-Objects into a tableView looks as follows:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("NameCell") as! PlayersCustomTableViewCell
if let arry = RLM_array {
let entry = arry.objects(NameEntry)[indexPath.row] as NameEntry
cell.playerLabel.text = entry.player
cell.accessoryType = entry.gameCompleted ? .None : .None
return cell
}
else {
cell.textLabel!.text = ""
cell.accessoryType = .None
return cell
}
}
Second question: How would I fill into the tableView only the RealmSwift-Objects that have a particular date (i.e. for example filling only the objects that have again the date above 15.06.2014). Or in other words, how would the above method only fill into the tableView the objects with the needed date-range ??
You can query Realm with dates.
If you want to get objects after a date, use greater-than (>), for dates before, use less-than (<).
Using a predicate with a specific NSDate object will do what you want:
let realm = Realm()
let predicate = NSPredicate(format: "date > %#", specificNSDate)
let results = realm.objects(NameEntry).filter(predicate)
Question 1: For the number of objects, just call count: results.count
Question 2: results is an array of NameEntrys after specificNSDate, get the object at indexPath. Example, let nameEntry = results[indexPath.row]
For creating a specific NSDate object, try this answer: How do I create an NSDate for a specific date?