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?
Related
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...
Is there a way to insert new items at the 0 index to the Realm container? I don't see an insert method in the Realm class.
Do I need to use Lists? If the answer is yes, how can I restructure the following code to be able to use Lists and keep the List in constant sync with the Realm container. In other words I'm having a hard time coming up with a good way to keep the Realm container and the List with the same items when adding and removing.
In the following code new items are entered at the last index. How can I restructure it to be able to insert items at the 0 index?
Model class
import RealmSwift
class Item:Object {
dynamic var productName = ""
}
Main ViewController
let realm = try! Realm()
var items : Results<Item>?
var item:Item?
override func viewDidLoad() {
super.viewDidLoad()
self.items = realm.objects(Item.self)
}
func addNewItem(){
item = Item(value: ["productName": productNameField.text!])
try! realm.write {
realm.add(item!)
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath)
let data = self.items![indexPath.row]
cell.textLabel?.text = data.productName
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete{
if let item = items?[indexPath.row] {
try! realm.write {
realm.delete(item)
}
tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
}
}
}
Ideally this is what I would like to be able to do when inserting new items in the addNewItem() method...
item = Item(value: ["productName": inputItem.text!])
try! realm.write {
realm.insert(item!, at:0)
}
Adding a sortedIndex integer property that lets you manually control the ordering of objects is definitely one of the more popular ways to order objects in Realm, however it's quite inefficient. In order to insert an object at 0, you'll then need to loop through every other object and increment its ordering number by 1, meaning you'll end up needing to touch every object of that type in your database in order to do it.
The best practice for this sort of implementation is to create another Object model subclass that contains a List property, keep one instance of it in Realm, and to then add every object to that. List properties behave like normal arrays, so it's possible to very quick and efficient to arrange objects that way:
import RealmSwift
class ItemList: Object {
let items = List<Item>()
}
class Item: Object {
dynamic var productName = ""
}
let realm = try! Realm()
// Get the list object
let itemList = realm.objects(ItemList.self).first!
// Add a new item to it
let newItem = Item()
newItem.productName = "Item Name"
try! realm.write {
itemList.items.insert(newItem, at: 0)
}
You can then use the ItemList.items object directly as the data source for your table view.
They're at least two ways to do this:
you can manually add priority as number in your class for.ex:
class Task: Item {
dynamic var something = ""
dynamic var priority = 0
}
add to realm:
//you can make priority 2,3,4 etc - 0 will be at the top
let task = Task(something: "Something", priority: 0)
and after retrieve objects:
var tasks: Results<Task>!
viewdidLoad(){
let realm = try! Realm()
tasks = realm.objects(Task.self).sorted(byKeyPath: "priority")
}
or you can make date like this:
class Task: Item {
dynamic var something = ""
dynamic var created = Date()
override class func indexedPriority() -> [String]{
return ["created"]
}
}
add to realm:
let task = Task(something: "Something")
and after retrieve objects:
var tasks: Results<Task>!
viewdidLoad(){
let realm = try! Realm()
tasks = realm.objects(Task.self).sorted(byKeyPath: "created")
}
first one you must update yourself to assign priority, second one will be automatically updated
Here's what I'm trying to do:
Provide a tableView that allows the user to select a formula name (an enum) that will be set as their default
In this view I'll place a checkmark next to the formula that is the current default
Their chosen default formula is what will determine which formula is used to calculate a 1 rep maximum amount (the theoretical amount the user could lift based on actual lifts of lesser weight)
After calculating the value, the user will be able to save a record of the lifts they did, the formula used to calculate the maximum, and the calculated maximum weight.
That record will be saved in Core Data with one of the entities being Lift which will simply be the formula name
I've created a class that I want to handle the work of providing the current default formula to what ever part of the app needs it as well as set the default when a new one is selected.
Here is my enum of the formula names:
enum CalculationFormula: Int {
case Epley
case Brzychi
case Baechle
case Lander
case Lombardi
case MayhewEtAl
case OConnerEtAl
}
and with help from the SO community, I created this class to handle the management of userDefaults:
class UserDefaultsManager: NSUserDefaults {
let formulas = [CalculationFormula.Baechle, CalculationFormula.Brzychi, CalculationFormula.Epley, CalculationFormula.Lander, CalculationFormula.Lombardi, CalculationFormula.MayhewEtAl, CalculationFormula.OConnerEtAl]
let formulaNameDictionary: [CalculationFormula : String] =
[.Epley : "Epley", .Brzychi: "Brzychi", .Baechle: "Baechle", .Lander: "Lander", .Lombardi: "Lombardi", .MayhewEtAl: "Mayhew Et.Al.", .OConnerEtAl: "O'Conner Et.Al"]
let userDefaults = NSUserDefaults.standardUserDefaults()
func getPreferredFormula() -> CalculationFormula? {
guard NSUserDefaults.standardUserDefaults().dictionaryRepresentation().keys.contains("selectedFormula") else {
print("No value found")
return nil
}
guard let preferredFormula = CalculationFormula(rawValue: NSUserDefaults.standardUserDefaults().integerForKey("selectedFormula")) else {
print("Wrong value found")
return nil
}
return preferredFormula
}
func setPreferredFormula(formula: CalculationFormula) {
NSUserDefaults.standardUserDefaults().setInteger(formula.rawValue, forKey: "selectedFormula")
}
You can see I have an array of the enums in the order I want them displayed in the tableView and a dictionary of the enums so I can get each enum's string representation to display in each cell of the tableView. Here's how I populate the cell text label which works:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("formulasCell", forIndexPath: indexPath)
let currentFormula = formulas[indexPath.row].formulaName
cell.textLabel?.text = currentFormula
return cell
}
and here's where I'm setting the checkmark anytime a cell in the tableView is selected
func refresh() {
let preferredFormula = defaults.getPreferredFormula()
for index in 0 ... formulas.count {
let indexPath = NSIndexPath(forItem: index, inSection: 0)
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
cell.accessoryType = preferredFormula == index ? .Checkmark : .None
}
}
}
As I mentioned at the beginning, I need to do many different things with this enum but to keep this question focused on one thing, I'll stay with this checkmark example which now doesn't work after creating my UserDefaultsManager class
The problem is obvious - preferredFormula is now an enum and I can't compare that to the index value which is an Int - but the solution is not. I could get the raw value of the enum but the rawValues aren't guaranteed to be in alignment with the cell indexPaths. Some ideas I've had are:
I could probably change the order of the enum cases so their raw values match the order I've put them in my formulas array, but that seems silly and unreliable
I could use the array index values but that seems equally silly and unreliable
If I just use the array, I don't have the string representations of the cases to display in the cells
It seems that using the array and dictionary together is a viable but the best I could come up with is maybe creating another dictionary that maps the enums to Ints but that would have the same issues I just listed.
Any guidance someone could provide would be greatly appreciated.
You seem to have made things a little more complicated than they need to be.
Firstly, you can use a String raw value for your enum and avoid the associated dictionary:
enum CalculationFormula: String {
case Epley = "Epley"
case Brzychi = "Brzychi"
case Baechle = "Baechle"
case Lander = "Lander"
case Lombardi = "Lombardi"
case MayhewEtAl = "Mayhew Et.Al."
case OConnerEtAl = "O'Conner Et.Al"
}
Second, Your UserDefaultsManager doesn't need to subclass NSUserDefaults, it is simply some utility functions. Also, you are doing a lot of checking in getPreferredFormula that you don't need to. I would suggest re-writing that class to use a computed property like so:
class UserDefaultsManager {
static let sharedInstance = UserDefaultsManager()
private var defaultsFormula: CalculationFormula?
var preferredFormula: CalculationFormula? {
get {
if self.defaultsFormula == nil {
let defaults = NSUserDefaults.standardUserDefaults()
if let defaultValue = defaults.objectForKey("selectedFormula") as? String {
self.defaultsFormula = CalculationFormula(rawValue: defaultValue)
}
}
return self.defaultsFormula
}
set {
self.defaultsFormula = newValue
let defaults = NSUserDefaults.standardUserDefaults()
if (self.defaultsFormula == nil) {
defaults.removeObjectForKey("selectedFormula")
} else {
defaults.setObject(self.defaultsFormula!.rawValue, forKey: "selectedFormula")
}
}
}
}
I have made the class a singleton; although this may have an impact on testability it simplifies issues that could arise if the default is changed in multiple places.
The appropriate place to set/clear the check mark is in cellForRowAtIndexPath so that cell reuse is handled appropriately. This code assumes that formulas is an array of CalculationFormula:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("formulasCell", forIndexPath: indexPath)
let currentFormula = formulas[indexPath.row]
cell.textLabel?.text = currentFormula.rawValue
let preferredFormula = UserDefaultsManager.sharedInstance.preferredFormula
cell.accessoryType = currentForumula == preferredFormula ? .Checkmark : .None
return cell
}
I'm playing around with custom cells.
With the great help from the stackoverflow community i've been able to put some code together. I'm able to fetch array values from a text string into a custom cell uilabel and uibutton, but the issue is - the fetched result is always the last object in the array.
Here is the code
func setUpQuestion()
{
// setting variables
var Question: String?
var option1: String?
// using a text string with custom separators
let text = ">>Here is the grocery question\n>>and another one\n--Apples\n-
-Oranges\n[pickApples]pickOranges\n[pickApples2]"
// splitting this string into four different arrays depending on the separator
let lines = split(text) { $0 == "\n" }
for line in lines {
if line.hasPrefix(">>") {
Question = line.substringFromIndex(advance(line.startIndex, 2))
} else if line.hasPrefix("[") {
if let index = line.rangeOfString("]")?.startIndex {
option1 = line.substringWithRange(Range<String.Index>(
start: advance(line.startIndex, 1), end: index))
}
}
}
// creating variables for appending the values - here I'm using a custom class called QuestionMark created in a separate .swift file
var question1 = QuestionMark(Question: Question!, option:option1!)
var question2 = QuestionMark(Question: Question!, option:option1!)
// appending the values into uilabel and uibutton in the custom cell
arrayOfQuestions.append(question1)
arrayOfQuestions.append(question2)
}
// regular tableView protocol functions
func tableView(tableView: UITableView, numberOfRowsInSection
section: Int) ->Int
{
return arrayOfQuestions.count
}
func updateCount(){
if let list = mainTableView.indexPathsForSelectedRows() as? [NSIndexPath] {
rowsCount.text = String(list.count)
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell
{
let cell: CustomCellForTableViewTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell") as! CustomCellForTableViewTableViewCell
// the SetCell function i'm using here was created in a separate .swift file
let quest = arrayOfQuestions[indexPath.row]
cell.setCell(quest.Questme!, optionone: quest.optionize!)
cell.optionOne.backgroundColor = UIColor.redColor()
return cell
}
Here are the additional codes i'm using for the class and setCell function
class QuestionMark
{
var Questme: String?
var optionize: String?
init(Question: String, option: String)
{
self.Questme = Question
self.optionize = option
}
// separate swift file
func setCell(Question: String, optionone: String)
{
self.mainText.text = Question
self.optionOne.setTitle(optionone, forState:UIControlState.Normal)
}
As a result in both cells i'm getting the last object from the text string and it looks like this
And another one - PickApples2
And another one - PickApples2
How do i start appending cells from the first array value and then move forward to second,third,fourth ?
Any ideas are greatly appreciated.
Thank you.
First of all, the syntax of the text to parse is pretty complicated ;-) …
Second of all, the problem to get always the last object is that you create the array of questions after the repeat loop. At that moment the variables question and option contain always the last found string.
Here a solution:
After getting a question a new object QuestionMark is created and appended to the array (without the optionize property)
After getting an option the appropriate QuestionMark object is fetched from the array by an index counter, the property optionize is set and the counter is increased.
Two notes:
Variable names should always start with a lowercase letter. Even the syntax highlighter of StackOverflow follows that naming convention.
In my solution all variables are non-optionals.
class QuestionMark
{
var questme: String
var optionize: String
init(question: String, option: String = "")
{
self.questme = question
self.optionize = option
}
...
var arrayOfQuestions = [QuestionMark]()
func setupQuestion() {
let text = ">>Here is the grocery question\n>>and another one\n--Apples\n--Oranges\n[pickApples]pickOranges\n[pickApples2]"
// splitting this string into four different arrays depending on the separator
var counter = 0
var question = ""
var option = ""
let lines = split(text) { $0 == "\n" }
for line in lines {
if line.hasPrefix(">>") {
question = line.substringFromIndex(advance(line.startIndex, 2))
let questionMark = QuestionMark(question: question)
arrayOfQuestions.append(questionMark)
} else if line.hasPrefix("[") {
if let index = line.rangeOfString("]")?.startIndex {
option = line.substringWithRange(Range<String.Index>(
start: advance(line.startIndex, 1), end: index))
let questionMark = arrayOfQuestions[counter]
questionMark.optionize = option
counter++
}
}
}
}
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)