Trying to add objects to Class and save info to Firebase database - swift

I want to save new objects via a view controller in my app. However, I want these new objects to load when the app is logged into. I am using firebase to save data into a database, but how can I save an object and have it return when the app is logged into again? I am new-ish to programming, sorry for any potential confusion.
Here is where the goal information is read when the app has been logged into.
for i in 0 ... clientList.count - 1 {
screenHandle = ref?.child(organizationCode).child(clientList[i].name).observe(.value, with: { (snapshot) in
let clientStuffLoad = snapshot.value as! [String:Any]
if clientStuffLoad["Goal 1 Description"] != nil {
clientList[i].goal1 = clientStuffLoad["Goal 1"] as! String
} else {
clientList[i].goal1 = ""
}
This is essentially what I have regarding adding a new member to the class Client:
#IBAction func addingClientSaveButton(_ sender: Any) {
var client7 = Client(name: addingClientName.text!,
goal1: addingClientGoal1.text!, goal2:
addingClientGoal2.text!,
goal3: addingClientGoal3.text!,
isSelected: false, s: 1,
ind: 1, targetBehavior1 : addingClientTB1.text!,
targetBehavior2 : addingClientTB2.text!,
targetBehavior3 : addingClientTB3.text!,
targetBehavior1Info : addingClientTB1Info.text!,
targetBehavior2Info : addingClientTB2Info.text!,
targetBehavior3Info : addingClientTB3Info.text!)
but I would like the object name to read the client name input as opposed to client7
The second part to this is that I want a way to write this to the database, and be able to read it at log in so that I can use the properties of the class and add to it when adding a new client.

This is a super broad question because it covers a lot of different aspects of working with Firebase; writing, reading, handling DataSnapshots etc. Also, I don't know what your data represents so I picked something for me to cover some of the aspects of working with Firebase.
There's no error checking but it works as is. I've commented along the way.
Firebase has no objects; just parent and child nodes. Everything can be thought of as key: value pairs like a dictionary. You cannot write an object or read an object. Only NSString, NSNumber, NSDictionary and the dreaded NSArray (or their Swift counterparts)
Let's start with a class - there's 100 ways to do this but I like classes to be responsible for their properties as well as accepting them and presenting them
class WineClass {
var wine_key = ""
var name = ""
var varietal = ""
//this is used when creating a new wine object before storing in firebase
init(withName: String, andVarietal: String) {
self.name = withName
self.varietal = andVarietal
}
//this is used when we are loading data from firebase to create the wineclass object
init(withSnapshot: DataSnapshot) {
let wineName = withSnapshot.childSnapshot(forPath: "wine_name").value as? String ?? "No Wine Name"
let wineDict = withSnapshot.value as! [String: Any]
let wineVarietal = wineDict["wine_varietal"] as? String ?? "No Wine Varietal"
self.wine_key = withSnapshot.key //when we read a wine, this will be it's reference in case we want to update or delete it
self.name = wineName
self.varietal = wineVarietal
}
//this is use to create a dictionary of key:value pairs to be written to firebase
func getWineDictForFirebase() -> [String: Any] {
let d = [
"wine_name": self.name,
"wine_varietal": self.varietal
]
return d
}
}
Then, we need a class var to store the WineClass's. This would be for example a dataSource for a tableView
var wineArray = [WineClass]() //a class var array to store my wines
Then I will give you two buttons, one that populates and writes some wine to Firebase and then a second that read them in and prints to console
func button0() {
self.writeWine(withName: "Scarecrow", andVarietal: "Red Blend")
self.writeWine(withName: "Ghost Horse", andVarietal: "Cabernet Sauvignon")
self.writeWine(withName: "Screaming Eagle", andVarietal: "Cabernet Sauvignon, Merlot, Cabernet Franc")
}
func button1() {
self.readWines()
}
And then the function that accepts some strings as properites for each wine and writes them to Firebase
func writeWine(withName: String, andVarietal: String) {
let newWine = WineClass(withName: withName, andVarietal: andVarietal) //create a new wine object
let wineListRef = self.ref.child("wine_list") //get a reference to my firebase wine_list
let thisWineRef = wineListRef.childByAutoId() //a new node for this wine
let d = newWine.getWineDictForFirebase() //get the wine properties as a dictionary
thisWineRef.setValue(d) //save it in firebase
}
and finally a function that reads in those wines, and prints their properties in console
func readWines() {
let wineRef = self.ref.child("wine_list")
wineRef.observeSingleEvent(of: .value, with: { snapshot in //we are reading in the entire wine node which will contain many child nodes
let allWines = snapshot.children.allObjects as! [DataSnapshot] //cast each child node as a DataSnapshot & store in array
for wineSnap in allWines { //iterate over each child node in the array
let wine = WineClass(withSnapshot: wineSnap) //create a new wine, ensuring we also keep track of it's key
self.wineArray.append(wine) //add to the array
}
for wine in self.wineArray {
print(wine.wine_key, wine.name, wine.varietal)
}
})
}
lastly, when button0 is clicked, our Firebase looks like this
wine_list
-LhbjhkEC8o9TUISCjdw
wine_name: "Scarecrow"
wine_varietal: "Red Blend"
-LhbjhkEC8o9TUISCjdx
wine_name: "Ghost Horse"
wine_varietal: "Cabernet Sauvignon"
-LhbjhkEC8o9TUISCjdy
wine_name: "Screaming Eagle"
wine_varietal: "Cabernet Sauvignon, Merlot, Cabernet Franc"
and then the output when button1 is clicked
-LhbjhkEC8o9TUISCjdw Scarecrow Red Blend
-LhbjhkEC8o9TUISCjdx Ghost Horse Cabernet Sauvignon
-LhbjhkEC8o9TUISCjdy Screaming Eagle Cabernet Sauvignon, Merlot, Cabernet Franc
Note that self.ref is a reference to the root node of my firebase yours will need to reference your firebase.

Related

ITunes Library Framework in Swift 5.2

I have searched everywhere and I am unable to find a swifty example of how to use the Apple ITunesLibraryFramework. I have been trying to figure out how to use this Framework in Swift 5.2.
I want to get information directly from the Music library rather than having to rely on a XML library export file.
I have the below code in my playground and it prints nothing legible. How would I access the fields for playlists and mediaItems and be able to read them in human readable form?
I have installed the framework in the project. This is my project playground code:
import Foundation
import iTunesLibrary
var library:ITLibrary
do {
let library = try ITLibrary(apiVersion: "1.1")
let mediaItems = library.allMediaItems
let playlists = library.allPlaylists
print("Media Folder Location - \(String(describing: library.mediaFolderLocation))")
print("\nPlaylists - \(playlists)")
print("\nTracks - \(mediaItems)")
} catch let error as NSError {
print(error)
}
This is the ITLibrary.m file that I imported the header:
#import <Foundation/Foundation.h>
#import <iTunesLibrary/iTunesLibrary.h>
When I run the above code in the project playground all I get is a bunch of binary data for both playlists and mediaItems. All I want to do is iterate over the data and collect information from the library for use in my program. It's probably something easy, but I haven't found it yet.
EDIT: - After using #Vincent's answer I ran into another problem with the following code:
import Foundation
import iTunesLibrary
let library = try ITLibrary(apiVersion: "1.1")
typealias tracks = [NSNumber:TrackInfo]
var trackInfo = TrackInfo()
struct TrackInfo {
var title = ""
var artist = ""
var album = ""
var totalTime = 0
var year = 0
var persistentID = ""
var location:URL!
}
let songs = library.allMediaItems
let playlists = library.allPlaylists
for playlist in playlists {
if playlist.name.lowercased().contains("test-") {
print("New Playlist---------------\nName: \(playlist.name)")
for song in playlist.items {
trackInfo.title = song.title
print("\n\nNew Track-----------------\nTitle: \(trackInfo.title)")
if song.artist?.name != nil {
trackInfo.artist = song.artist?.name as! String
}
print("Artist: \(trackInfo.artist)")
trackInfo.album = song.album.title!
print("Albumn Name: \(trackInfo.album)")
trackInfo.totalTime = song.totalTime
print("Total Time: \(trackInfo.totalTime)")
trackInfo.year = song.year
print("Year: \(trackInfo.year)")
trackInfo.location = song.location!
print("Location: \(trackInfo.location!)")
var persistentID = song.persistentID
tracks.updateValue(song.persistentID, trackInfo)
}
}
}
The issue I'm having is getting the tracks info into the trackInfo dictionary. I'm trying to use the track persistentID (NSNumber) as the key for the dictionary, which I have declared. For some reason it isn't allowing me to use it.
Here's how you can have it print each playlist and track:
Each ITLibPlaylist or ITLibMediaItem object contains many information about each playlist/media item. To get only the name/title of each, you will have to iterate through the results to retrieve them.
For this example below, the name of each playlist's name is printed.
print("\nPlaylists -")
for playlist in playlists {
print(playlist.name)
}
Which will print (for example):
Playlists -
Library
Music
For this example below, the name of each track's name is printed.
print("\nTracks -")
for mediaItem in mediaItems {
print(mediaItem.title)
}
Which will print (for example):
Tracks -
Ev'ry Time We Say Goodbye
My Favorite Things
But Not for Me
Summertime
Edit: Here's the secondary solution to the secondary problem:
First things first, a dictionary should be initialised, instead of using typealias.
typealias only makes an alias for a pre existing type, like
typealias NumberWithAlotOfDecimals = Double
let a: NumberWithAlotOfDecimals = 10.1
let b: Double = 10.1
both will a and b are Double, as NumberWithAlotOfDecimals is just an alias for Double.
Here's how to initialise:
//typealias tracks = [NSNumber:TrackInfo] // not this
var tracks = [NSNumber:TrackInfo]() // but this
Secondly, nullable objects should be properly handled
if let artistName = song.artist?.name {
trackInfo.artist = artistName
}
and
if let title = song.album.title {
trackInfo.album = title
}
if let location = song.location {
trackInfo.location = location
print("Location: \(location)")
}
instead of
if song.artist?.name != nil {
trackInfo.artist = song.artist?.name as! String
}
Please do not use ! to force unwrap nullable objects as that will cause runtime crashes when the object is nil.
Lastly, this is the way to store key value into dictionary in Swift.
let persistentID = song.persistentID
//tracks.updateValue(song.persistentID, trackInfo) // not this
tracks[persistentID] = trackInfo // this

How to use NSSet created from Core Data

I have the following core data model:
where Person to Codes is a one-to-many relationship.
I have a function which returns a Person record and if the code person.codes returns an NSSet of all the codes associated with that Person. The issue that I am having is how to use the NSSet.
person.codes.allObjects.first returns this data:
<Codes: 0x60000213cb40> (entity: Codes; id: 0xb978dbf34ddb849 <x-coredata://A2B634E4-E136-48E1-B2C5-82B6B68FBE44/Codes/p1> ; data: {
code = 4LQ;
number = 1;
whosAccount = "0xb978dbf34ddb869 <x-coredata://A2B634E4-E136-48E1-B2C5-82B6B68FBE44/Person/p1>";
})
I thought if I made person.codes.allObjects.first of type Codes, I would be able to access the code and number elements but I get an error: error: value of type 'Any?' has no member 'number'
Also, how can I search this data set for a particular code or number.
I appreciate that this is proabably a simple question but have searched and read the documentation to no avail. I suspect that may base knowledge is not sufficient.
Update
I have a CoreDataHandler class which contains the following code:
class CoreDataHandler: NSObject {
//static let sharedInstance = CoreDataHandler()
private static func getContext() -> NSManagedObjectContext {
let appDelegate = NSApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
static func fetchPerson() -> [Person]? {
let context = getContext()
do {
let persons: [Person] = try context.fetch(Person.fetchRequest())
return persons
} catch {
return nil
}
}
I can fetch a person using:
let row = personTableView.selectedRow
let person = CoreDataHandler.fetchPerson()?[row]
Core Data supports widely native Swift types.
Declare codes as Set<Codes> in the Person class.
It's much more convenient than typeless NSSet.
You get a strong type and you can apply all native functions like filter, sort, etc. without type cast.
let codes = person.codes as! Set<Code>
Once that is done you can access the properties. Searching can be done by filtering for instance
let filteredCodes = codes.filter({ $0.code == "XYZ" })
will return all objects that has the code "XYZ". Or to get only one you can use
let code = codes.first(where: {$0.id == 1})
which will return the first object that has id = 1
A simple example getting all Person objects that has a given code
func findWithCode(_ code: String) -> [Person] {
guard let persons = CoreDataHandler.fetchPerson() else {
return []
}
var result = [Person]()
for person in persons {
let codes = person.codes as! Set<Code>
if codes.contains(where: { $0.code == code }) {
result.append(person)
}
}
return persons
}

databaseReference.observe(DataEventType.value, with:{(DataSnapshot) not working properly all the time

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.

realm. remove objects from ListBase

I have different realm models. They have List properties. I want to make universal way for removing objects from List properties. So I did the following:
if let list = self[property.name] as? ListBase {
list._rlmArray.removeAllObjects()
}
but this just clear list property, without deleting objects from realm. The only way I've found is:
if let list = self[property.name] as? ListBase {
while list.count > 0 {
let object = list._rlmArray.firstObject()
let any = object as Any
if let theObject = any as? Object {
realm.delete(theObject)
}
}
}
Code above works and doesn't generate any warning. But it looks ugly.
You can use dynamicList(_ propertyName: String) to retrieve List property by name instead subscript.
if property.type == .array {
try! realm?.write {
realm?.delete(dynamicList(property.name))
}
}

Deleting with one-to-many relationship

I have a one-to-many relationship:
class GameSystem: Object {
dynamic var gameSystemName = ""
}
class games: Object {
dynamic var gameSystemName = gameSystemName().name
dynamic var gameTitle = ""
dynamic var gameGenre = ""
}
The gameSystemNames are currently displayed on a TableView. If the user deletes a gameSystemName, I want that gameSystemName along with all of that system's games deleted.
The code I'm currently using will only delete the GameSystem, but leaves all the games.
func deleteRowAtIndexPath(indexPath: NSIndexPath) {
let realm = Realm()
let objectToDelete = gameSystems[indexPath.row]
realm.write {
realm.delete(objectToDelete)
}
gameSystemTableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
gameSystems = Realm(path: Realm.defaultPath).objects(GameSystem)
}
I'm assuming there's a simple way to do this.
If you keep your model as it is, the solution would be to query first for the relevant objects of the relation Game:
// …
let objectToDelete = gameSystems[indexPath.row]
let gameSystemName = objectToDelete.gameSystemName
realm.write {
let games = realm.objects(Game).filter("gameSystemName = ?", gameSystemName)
realm.delete(games)
realm.delete(objectToDelete)
}
// …
Model Recommendation
I'd propose instead that you add an explicit link to your model, instead of expressing the relationship through a loosely linked foreign key. But the object-mapping is very individual and may be dependent on further constraints going beyond the scope of your question and this answer. For further reference, that would look like below:
class GameSystem : Object {
dynamic var name = ""
let games = List<Game>()
}
class Game : Object {
dynamic var title = ""
dynamic var genre = ""
// Use a backlink
// (https://realm.io/docs/swift/latest/#inverse-relationships)
dynamic var gameSystem: GameSystem? {
return linkingObjects(GameSystem.self, forProperty: "games").first
}
}
If you setup your model like this, you can delete your games very easy:
// …
let objectToDelete = gameSystems[indexPath.row]
realm.write {
realm.delete(objectToDelete.games)
realm.delete(objectToDelete)
}
// …
Note: In the future, Realm will bring Cascading Deletes as feature. Once that is released, you won't even need to take care of the manual deletion of associated games, but you will rather be able to declare the strong relationship in your model, so that the Games are automatically deleted.
Alternative Link Declaration
You can also declare your link vice-versa, but that would make it likely harder to use a feature like Cascading Deletes in the future. However the code to delete them for now would look the same as above.
class GameSystem : Object {
dynamic var name = ""
// Use a backlink
// (https://realm.io/docs/swift/latest/#inverse-relationships)
dynamic var games: [Game] {
return linkingObjects(Game.self, forProperty: "gameSystem")
}
}
class Game : Object {
dynamic var title = ""
dynamic var genre = ""
let gameSystem: GameSystem? = nil
}
It is very easy to delete the Parent as well as childrens in REALM SWIFT... You just need to write a small piece of code.
Here I am trying to delete my main category called "Swift" and whenever my main category gets deleted all the Sub Categories are also deleted...
do{
try realm.write({
realm.delete(Category.items)
realm.delete(Category)
})
}catch{
print("ERROR WHILE DELETING CELL ::: \(error)")
}
In short to delete subcategories all you need is to place "items" after . followed by parent name.