I am developing an app where you can add items to a Table View using the Realm Database. At the moment, I can add an Item to the Table View but it's being added twice to the Realm Database. This then means that if I shut my app & then go back into it, the new Item I had previously added appears twice, once in the new position & once replacing the item which was in the table view just above it. Attached is my code. I've tried various different things but just can't figure out how to fix it. Thank you for your help!
This is the code where I am adding the item to my Table View/Realm Database:
extension MainTableViewController {
func createNewEventAlertView () {
let newEventAlert = PMAlertController(title: "New Event Alert", description: "", image: nil, style: .alert)
newEventAlert.addTextField { (eventNameAlertTextField) in
eventNameAlertTextField?.placeholder = "Event Name"
eventNameAlertTextField?.adjustsFontSizeToFitWidth = true
eventNameAlertTextField?.textAlignment = .center
eventNameAlertTextField?.text! = nameOfEvent.text!
nameOfEvent = eventNameAlertTextField!
}
newEventAlert.addTextField { (dayEventDateTextField) in
dayEventDateTextField?.placeholder = "Day Of The Month"
dayEventDateTextField?.inputView = dayPickerView
dayOfEvent = dayEventDateTextField!
}
newEventAlert.addTextField { (monthEventDateTextField) in
monthEventDateTextField?.placeholder = "Month"
monthEventDateTextField?.inputView = monthPickerView
monthOfEvent = monthEventDateTextField!
}
newEventAlert.addTextField { (remindOneDayBeforeTextField) in
remindOneDayBeforeTextField?.placeholder = "Remind 1 Day Before?"
remindOneDayBeforeTextField?.inputView = oneDayPickerView
oneDayReminder = remindOneDayBeforeTextField!
}
newEventAlert.addTextField { (remindSevenDaysBeforeTextField) in
remindSevenDaysBeforeTextField?.placeholder = "Remind 7 Days Before?"
remindSevenDaysBeforeTextField?.inputView = sevenDayPickerView
sevenDayReminder = remindSevenDaysBeforeTextField!
}
newEventAlert.addAction(PMAlertAction(title: "Add", style: .default, action: {
print("Add Event Button Pressed in Alert")
//Adding realmEventItem & its variables to the Database
try! realm.write {
//Adding the name & date of the Event to the Realm Database Item
self.realmEventItem.nameOfEventRealm = self.nameOfEvent.text!
let fullDateOfEvent = self.dayOfEvent.text! + " " + self.monthOfEvent.text!
self.realmEventItem.dateOfEventRealm = fullDateOfEvent
//Telling the Realm Database whether they wan't to be reminded one & seven days before the event
if self.oneDayReminder.text == "Yes" {
self.realmEventItem.oneDayBeforeReminderRealm = true
} else {self.realmEventItem.oneDayBeforeReminderRealm = false}
if self.sevenDayReminder.text == "Yes" {
self.realmEventItem.sevenDaysBeforeReminderRealm = true
} else {self.realmEventItem.sevenDaysBeforeReminderRealm = false}
// Making Sure there is something written in the Name & Date Text Fields before allowing anything to be added
if self.nameOfEvent.text != "" && self.dayOfEvent.text != "" && self.monthOfEvent.text != "" {
/*
- use create method instead of add method to add new entries,
- Because add method replaces last new entry with our previous entry,
- so every time only one object store in database
*/
realm.create(RealmEventItem.self, value: self.realmEventItem, update: false)
self.tableView.insertRows(at: [IndexPath.init(row: realmEventList.count-1, section: 0)], with: .automatic)
//By Adding the follwing line it has meant that my code no longer crashes when I add an Item, then delete it & then try and add another item again.
self.tableView.numberOfRows(inSection: realmEventList.count)
print(realmEventList.count)
self.labelWhenTableViewIsEmpty.text = ""
print("Add to Table View Called")
} else {
// Missing Boxes
print("Missing Information!")
}
// This makes sure that each time the user goes to add a new Event all the picker are reset
if self.nameOfEvent.text != "" { self.nameOfEvent.text = "" }
if self.dayPickerView.selectedRow(inComponent: 0) != 0 { self.dayPickerView.selectRow(0, inComponent: 0, animated: false) }
if self.monthPickerView.selectedRow(inComponent: 0) != 0 {self.monthPickerView.selectRow(0, inComponent: 0, animated: false)}
if self.oneDayPickerView.selectedRow(inComponent: 0) != 0 {self.oneDayPickerView.selectRow(0, inComponent: 0, animated: false)}
if self.sevenDayPickerView.selectedRow(inComponent: 0) != 0 {self.sevenDayPickerView.selectRow(0, inComponent: 0, animated: false)}
}
print("REAL DATA:::-- \(realm.objects(RealmEventItem.self))")
}))
newEventAlert.addAction(PMAlertAction(title: "Cancel", style: .cancel, action: {
print("Cancel Button Pressed in Alert")
}))
present(newEventAlert, animated: true, completion: nil)
}
}
I have managed to fix the problem by using a new variable of type RealmEventItem()
The Code Changed (which is inside try! realm.write) is now as follows:
try! realm.write {
//The concept of adding a newEventItem of type RealmEventItem() & and then adding all the values to it rather than direct to the RealmEventItem() fixed the problem of the new items replacing the previous ones.
let newEventItem = RealmEventItem()
// Joining the Day & Month Together
let fullDateOfEvent = self.dayOfEvent.text! + " " + self.monthOfEvent.text!
// Making Variables to give a boolean of whether the user would like reminders
var oneDayReminderItem = false
var sevenDayReminderItem = false
if self.oneDayReminder.text == "Yes" {
oneDayReminderItem = true
} else {oneDayReminderItem = false}
if self.sevenDayReminder.text == "Yes" {
sevenDayReminderItem = true
} else {sevenDayReminderItem = false}
//Setting the values to the newEventItem --> which is part of the RealmEventItem()
newEventItem.nameOfEventRealm = self.nameOfEvent.text!
newEventItem.dateOfEventRealm = fullDateOfEvent
newEventItem.oneDayBeforeReminderRealm = oneDayReminderItem
newEventItem.sevenDaysBeforeReminderRealm = sevenDayReminderItem
// Making Sure there is something written in the Name & Date Text Fields before allowing anything to be added
if self.nameOfEvent.text != "" && self.dayOfEvent.text != "" && self.monthOfEvent.text != "" {
/*
- use create method instead of add method to add new entries,
- Because add method replaces last new entry with our previous entry,
- so every time only one object store in database
*/
//realm.add(newEventItem, update: false)
realm.create(RealmEventItem.self, value: newEventItem, update: false)
self.tableView.insertRows(at: [IndexPath.init(row: realmEventList.count-1, section: 0)], with: .automatic)
//By Adding the following line it has meant that my code no longer crashes when I add an Item, then delete it & then try and add another item again.
self.tableView.numberOfRows(inSection: realmEventList.count)
print(realmEventList.count)
self.labelWhenTableViewIsEmpty.text = ""
print("Add to Table View Called")
} else {
// Missing Boxes
print("Missing Information!")
}
}
Hope this can help someone in the future!
Related
This function is not acting as expected. I'm trying to nil a set of fields.The earlier section gets the correct field names, and is used in other functions. I've got about ten tables, and they all share the same context, in case that matters.
The first unexpected thing is that "yes, changes" never runs, so I presume that the settings object is detached from its context. Or perhaps CoreData treats nil as some kind of exception to triggering the .hasChanges flag?
When it runs, the save throws no errors, and the object displays as expected, displayed with the values set to nil. But there are no changes in the db.
I can save data into these fields without problem, and confirm that in the db; this problem only happens with setting the value to nil.
static func clearSettings(_ s : Set<PaletteElementType>? = nil) {
guard var setting = activeSetting() else {return}
print(setting.id)
let cats = s ?? PaletteView.compCatButtons
let tgts = setting.getAssociatedFieldNames(tgts: cats, clearing : true, values: false)
for (key, val) in tgts {
var src = Set((val as? Dictionary<FieldNameSuffixes, String>)!.values)
if key == .catBgndButton {
src = src.union(["opacity", "opacityStart", "opacityStartDelta","opacityEnd", "opacityEndDelta", "opacityTimer"])
}
for s in src {
print(s)
setting.setNilValueForKey(s)
if Blocks.context!.hasChanges {
print("yes, changes")
}
do {
try Blocks.context!.save()
print("deleted \(setting.value(forKey: s))")
} catch { print("deadly dogs")}
}
print("val is \(setting)")
}
}
OK, working when I do it this way:
static func clearSettings(_ s : Set<PaletteElementType>? = nil) {
guard var setting = activeSetting() else {return}
print(setting.id)
let cats = s ?? PaletteView.compCatButtons
let tgts = setting.getAssociatedFieldNames(tgts: cats, clearing : true, values: false)
for (key, val) in tgts {
var src = Set((val as? Dictionary<FieldNameSuffixes, String>)!.values)
if key == .catBgndButton {
src = src.union(["opacity", "opacityStart", "opacityStartDelta","opacityEnd", "opacityEndDelta", "opacityTimer"])
}
for n in setting.entity.attributesByName.enumerated() {
if src.contains( n.element.key as String) {
print("found one")
setting.setNilValueForKey(n.element.key)
}
}
do {
try Blocks.context!.save()
} catch {print("bumpy beasts")}
print("val is \(setting)")
}
}
Happy it's working, but I don't really understand the distinction here. What is the better way to handle this? I'm not chasing some super performant code, so I don't mind a few extra loops... but what's the deal?
I use xCode 9, Swift 4 and "Eureka form library" for my project.
The situation :
I have form with a list and a button.
I need help with these 2 issues :
when click on button I want to print the selected value
I want to be able to set for the list an element as a default selected value
My code :
import UIKit
import Eureka
class myPage: FormViewController {
override func viewDidLoad() {
super.viewDidLoad()
createForm()
}
func createForm(){
form
+++ Section("Sample list ")
form +++ SelectableSection<ListCheckRow<String>>("Continents", selectionType: .singleSelection(enableDeselection: false))
let continents = ["Africa", "Antarctica", "Asia", "Australia", "Europe", "North America", "South America"]
for element in continents {
form.last! <<< ListCheckRow<String>(element){ listRow in
listRow.title = element
listRow.selectableValue = element
listRow.value = nil
}
}
form.last! <<< ButtonRow("Button1") {row in
row.title = "Get List Value"
row.onCellSelection{[unowned self] ButtonCellOf, row in
print ("Selected List Value = ????????")
}
}
}
Thanks in advance.
For printing all the form value:
print(form.values())
This will print the dictionary for the all form values keyed by the row tag.
For this case it prints like this (Australia is selected):
["Asia": nil,
"Africa": nil,
"Antarctica": nil,
"Australia": Optional(
"Australia"), "Europe": nil,
"South America": nil,
"Button1": nil,
"North America": nil]
Eureka's SelectableSection also have selectedRow() (for multiple selection selectedRows()) methods.
So you can get selected values like this:
First just add tag to SelectableSection to a tag.
form +++ SelectableSection<ListCheckRow<String>>("Continents", selectionType: .singleSelection(enableDeselection: false)) { section in
section.tag = "SelectableSection"
}
Now on the button selection
form <<< ButtonRow("Button1") { row in
.. // button setup
}.onCellSelection { [unowned self] (cell, row) in
if let section = self.form.section(by: "SelectableSection") as?
SelectableSection<ListCheckRow<String>> {
print(section.selectedRow()?.value ?? "Nothing is selected")
}
}
Now For Default value selection:
let defaultContinent = "Antarctica"
Now in Button's onCellSelection:
}.onCellSelection { [unowned self] (cell, row) in
.. // printing the selected row as above
if let row = self.form.row(by: defaultContinent) as? ListCheckRow<String> {
row.selectableValue = defaultContinent
row.didSelect()
}
}
func checkPaid(utilityId : String) -> Int{
var amount:String = ""
var status = 0
print("inside new function ")
print ("\(utilityId) inside new function ")
self.databaseRefPayment.observe(DataEventType.value, with:{(DataSnapshot) in
if DataSnapshot.childrenCount > 0 {
for payments in DataSnapshot.children.allObjects as! [DataSnapshot]{
var paymentsObject = payments.value as? NSDictionary
/*
if(paymentsObject!["month"] as! String == monthCheck && paymentsObject!["year"] as! String == monthCheck && paymentsObject!["utilityid"] as! String == utilityId as! String){ */
if(paymentsObject!["utilityId"] as! String == utilityId){
amount = paymentsObject!["amount"] as! String
print(amount)
print("Ypur program is working perfect")
status = 1
}
}
}
})
return status
}
The above function is filtering the data present in payments node based on the value for utilityId getting passed in the function . But the strange thing is observe(DataEventType.value, with:{(DataSnapshot) this event is not getting triggered all the time . Its just skipping that portion unnecessarily . I am very new to firebase and getting really mad with these kind of unpredicted behaviours . Please help me in this . feel free to ask for any clarifications .
The firebase executes firebase query functions in different thread , so after u call check paid(), it runs the checkpaid() firebase query in another thread,and it will return from the function , eventhough ur query is running in the background..so it will seem like,checkpaid() is not working , but actually it's running on another thread.
I think you first fetch all the required data from payment, and store it in a list , and then use that list to compare with utility.
Every time this function is called it adds/resets the Key-Value Observer for whichever child node you are observing it doesn't actually check the value unless it is changed. I believe it is your intention to call checkPaid(utilityId:) to check the child is 'paid' by some means. There is no need to add a KVO if you are directly reading the value for a single snapshot. consider the following:
func checkPaid(utilityId: String) -> Bool {
//Assume it is not paid if we cannot verify it.
var isPaid = false
//Create a new reference to Firebase Database
var ref: DatabaseReference!
ref = Database.database().reference().child(utilityId)
//Get the values for the child, test if it is paid or not.
ref.queryOrderedByValue().observeSingleEvent(of: .value) { (snapshot) in
if (snapshot.value is NSNull) {
print("No Child With \(utilityId) Exists")
} else {
//child with utilityId exists, in case multiple utilityId's exist with the same value..
for child in snapshot.children.allObjects as! [DataSnapshot] {
if let values = child.value as? [String : AnyObject] {
let uid = child.key //utilityId
var month:String = ""
var year:String = ""
var amount:String = ""
//var amount:Double = 0.0
//get values from parent
if let m = values["month"] as? String {
month = m
}
if let y = values["year"] as? String {
year = y
}
if let a = values["amount"] as? String {
amount = a
}
/*
if let a = values["amount"] as? Double {
amount = a
}
*/
//??
if ((month == monthCheck) && (year == monthCheck)) {
isPaid = true
}
}
}
}
return isPaid
}
I am making one assumption here; that utilityId is the key for the child.
if you have parent nodes to utilityId you'll have to transverse those as well when you reference the database:
ref = Database.database().reference().child(utilities).child(utilityId) ..etc
If you need a KVO to update a local property I suggest adding/calling it in viewDidLoad, it's completion handler should take care of updating whichever properties are updated when they change in Firebase.
I'm currently trying to validate a sign up page using an if statement with multiple conditions.
At the same time, I want to validate the email address with a correct email format.
Below is each field should essentially be checked.
#IBAction func createAccountButton(_ sender: Any) {
//check if textfields are empty and display alert
let providedEmailAddress = emailTextField.text
let isEmailAddressValid = isValidEmailAddress(emailAddressString: providedEmailAddress!)
if ( isEmailAddressValid || (firstNameTextField.text?.isEmpty)! || (lastNameTextField.text?.isEmpty)! || (usernameTextField.text?.isEmpty)! || (phoneNumberTextField.text?.isEmpty)! || (passwordTextField.text?.isEmpty)!) {
let alertViewController = SCLAlertView().showInfo("Failed to create account", subTitle: "One or more fields are empty, please check and try again.")
} else {
SVProgressHUD.show(withStatus: "Creating account...")
SVProgressHUD.dismiss(withDelay: 4)
}
}
The variable isEmailAddressValid is linked to this function I found on this site.
// Check if email address entered is of the valid format
func isValidEmailAddress(emailAddressString: String) -> Bool {
var returnValue = true
let emailRegEx = "[A-Z0-9a-z.-_]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,3}"
do {
let regex = try NSRegularExpression(pattern: emailRegEx)
let nsString = emailAddressString as NSString
let results = regex.matches(in: emailAddressString, range: NSRange(location: 0, length: nsString.length))
if results.count == 0
{
returnValue = false
}
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
returnValue = false
}
return returnValue
}
However, when testing I realised when I enter a value in each field except the emailTextfield.text and click to create an account, the else part of the statement is executed.
Moreover when all fields have a value, I would assume the else part of the statement WILL execute, but the 'failed to create an account' alert is displayed instead.
What am I doing wrong and what needs to be changed?
You should replace this part
if ( isEmailAddressValid |
with
if ( !isEmailAddressValid |
Why is the realm-list containing the very same elements instead of different ones ?
As you can see in the picture below, there are two relam-objects (UndoMemoryNameEntry and NameEntry). The first one contains a list of 8 elements. The list's element-type is of type NameEntry !
My last NameEntry object is written with currentScorePlayer=1 and currentScoreMe=15 as you can see in the picture below:
The list in UndoMemoryNameEntry is correctly inserted the last NameEntry object. You find the insertion-code further down...
But now the problem: Why are all the existing list-elements as well changed to the newest inserted element ???? As you can see in the picture below, all the elements are unfortunately identical to the last one added - why ??????
If I change the NameEntry to the following :
And inserting at index=0 to the list, then the List changes to :
Why are all the elments changed ? And not just the inserted one ??? Thanks for any help on this !
My two realm-objects are :
class NameEntry: Object {
dynamic var playerName = ""
dynamic var isMyAdversary: Bool = false
dynamic var currentScorePlayer: Int = 0
dynamic var currentScoreMe: Int = 0
}
and the List :
class UndoMemoryNameEntry: Object {
dynamic var undoPlayerName = ""
let NameEntryList = List<NameEntry>()
}
The following code creates the Realm-List :
// query rlm for existing object (with name adversary
let undoPredicate = NSPredicate(format: "undoPlayerName == %#", adversaryName)
let undoPlayerName = rlm.objects(UndoMemoryNameEntry).sorted("undoPlayerName", ascending: true).filter(undoPredicate)
// if undoPlayerName object does not exist - then create it!
if (undoPlayerName.count < 1) {
rlm.beginWrite()
let undoEntry = UndoMemoryNameEntry()
undoEntry.undoPlayerName = adversaryName
rlm.add(undoEntry)
rlm.commitWrite()
}
The following code adds a "NameEntry"-Element in the List :
let undoPredicate = NSPredicate(format: "undoPlayerName == %#", plaNameLab)
let undoPlayerName = rlm.objects(UndoMemoryNameEntry).sorted("undoPlayerName", ascending: true).filter(undoPredicate)
if (undoPlayerName.count == 1) {
rlm.beginWrite()
println(entry)
var undoEntry = undoPlayerName[0] as UndoMemoryNameEntry
undoEntry.NameEntryList.insert(entry, atIndex: 0)
rlm.commitWrite()
}
The above code-excerts work perfectly - except that the realm-List always changes all its elements to the one just inserted.
I finally found a solution:
First of all rearrange the two realm objects as follows:
class NameEntry: Object {
dynamic var playerName = ""
dynamic var currentScorePlayer: Int = 0
dynamic var currentScoreMe: Int = 0
// the undo-list is better placed in the first object...
let undoEntryList = List<UndoMemoryNameEntry>()
override static func primaryKey() -> String? {
return "playerName"
}
}
class UndoMemoryNameEntry: Object {
dynamic var undoPlayerName = ""
dynamic var currentScorePlayer: Int = 0
dynamic var currentScoreMe: Int = 0
// no primary key here since the undoEntry will have several items with the same undoPlayerName
}
Then when adding a "NameEntry"-Element in the List :
let predicate = NSPredicate(format: "playerName == %#", plaNameLab)
let playerName = rlm.objects(NameEntry).sorted("playerName", ascending: true).filter(predicate)
if (playerName.count == 1) {
rlm.beginWrite()
var entry = playerName[0] as NameEntry
// you need to create a new list object first !!!!!!!!!!!!
// ...in my initial example, this creation was missing !!!!!!
var siblingEntry = UndoMemoryNameEntry()
siblingEntry.undoPlayerName = plaNameLab
siblingEntry.currentScorePlayer = entry.currentScorePlayer
siblingEntry.currentScoreMe = entry.currentScoreMe
// insert new list-element
entry.undoEntryList.insert(siblingEntry, atIndex: 0)
// alternatively choose append if you want to add the element at the end of the list
entry.undoEntryList.append(siblingEntry)
// or choose the "ringbuffer-solution" given in the add-on below if you want to restrict the number of list-elements to ringbuffer-size !
// ...
rlm.commitWrite()
}
Add-on: If you want to create a ringbuffer having only a limited number of list-elements:
// create ringbuffer of 20 elements (20th element will be newest)
let ringBufferSize = 20
let undoPredicate = NSPredicate(format: "undoPlayerName == %#", plaNameLab)
if (entry.undoEntryList.filter(undoPredicate).sorted("undoPlayerName").count < ringBufferSize) {
entry.undoEntryList.append(siblingEntry)
}
else {
// entry.undoEntryList.replace(ringBufferSize-1, object: siblingEntry)
entry.undoEntryList.removeAtIndex(ringBufferSize-1)
entry.undoEntryList.append(siblingEntry)
for index in 0..<ringBufferSize-1 {
let tempEntry1 = rlm.objects(UndoMemoryNameEntry).filter(undoPredicate).sorted("undoPlayerName")[index] as UndoMemoryNameEntry
let tempEntry2 = rlm.objects(UndoMemoryNameEntry).filter(undoPredicate).sorted("undoPlayerName")[index+1] as UndoMemoryNameEntry
tempEntry1.currentScorePlayer = tempEntry2.currentScorePlayer
tempEntry1.currentScoreMe = tempEntry2.currentScoreMe
}
let tempEntry = rlm.objects(UndoMemoryNameEntry).filter(undoPredicate).sorted("undoPlayerName")[ringBufferSize-1] as UndoMemoryNameEntry
rlm.delete(tempEntry)
}