One to many relations Parse - ios

I've looked all over but I can't find an answer to this question.
I am saving a Podcasts and its related episodes to Parse but the following code only saves 1 episode and the podcast (I suppose every entry found in the for loop resets currentP and only the last value found gets saved).
let currentP = PFObject(className: self.podcastClass)
currentP["user"] = PFUser.currentUser()
currentP["name"] = name
currentP["artist"] = artist
currentP["summary"] = summary
currentP["feedURL"] = feedURL
currentP["artworkURL"] = artworkURL
currentP["artwork"] = artwork
currentP["date"] = date
let episodesToParse = PFObject(className: self.episodesClass)
for episode in episodes {
episodesToParse["showDate"] = episode.date
episodesToParse["title"] = episode.title
episodesToParse["downloadURL"] = episode.enclosures[0].valueForKey("url") as? String
episodesToParse["showNotes"] = episode.summary
episodesToParse["localPath"] = ""
episodesToParse["isDownloaded"] = "no"
episodesToParse["parent"] = currentP
}
episodesToParse.saveInBackground()
If I use something like episodesToParse.addObject(episode.date, forKey: "showDate") then the following error is returned:
[Error]: invalid type for key showDate, expected date, but got array (Code: 111, Version: 1.8.1)
I'm not sure how to proceed. What I want is currentP to be saved as it is and all its episodes to be saved in a different class with a relationship to its parent (Podcast). I found tons of ways to do this if you're adding one episode at a time but not a whole bunch of them (I would like to be able to save 500 instance of episodesToParseat once.
Thanks for your help.

Your problem is, that you save the episodesToParse after the loop. You have to move the episodesToParse.saveInBackground() inside the loop so that everytime the loop sets the properties of the episode the episode gets updated:
for episode in episodes {
episodesToParse["showDate"] = episode.date
episodesToParse["title"] = episode.title
episodesToParse["downloadURL"] = episode.enclosures[0].valueForKey("url") as? String
episodesToParse["showNotes"] = episode.summary
episodesToParse["localPath"] = ""
episodesToParse["isDownloaded"] = "no"
episodesToParse["parent"] = currentP
//Inside
episodesToParse.saveInBackground()
}
Or you could use PFObject.saveAllInBackground to save all objects:
var episodesToSave[PFObject] = []
for episode in episodes {
var episodeToParse
episodeToParse["showDate"] = episode.date
episodeToParse["title"] = episode.title
episodeToParse["downloadURL"] = episode.enclosures[0].valueForKey("url") as? String
episodeToParse["showNotes"] = episode.summary
episodeToParse["localPath"] = ""
episodeToParse["isDownloaded"] = "no"
episodeToParse["parent"] = currentP
//Add to episode-array
episodesToSave.append(episodesToParse)
}
//Save all objects in the array
PFObject.saveAllInBackground(episodesToSave)

Related

Core data taking time to insert records with fetching entity & set as relationship

I have 2 entities: Components & SurveyReadingWeb that have a one-to-many relationship.
I have saved Components data in core data database; but while saving SurveyReadingWeb -- in a for loop -- I have to fetch Components data entity by passing component id and setting the surveyreadings isComponentexists relationship, like in the code below for 8000 records. This process is taking too much time, nearly 7 minutes. How can I reduce the time?
for item in items {
autoIncrementId = autoIncrementId + 1
print("reading items parsed:- \(item.SurveyReadingId), \(autoIncrementId)")
let reading : SurveyReadingWeb? = NSEntityDescription.insertNewObject(forEntityName: Constants.EntityNames.kSURVEYREADINGWEB, into: self.managedContext) as? SurveyReadingWeb
reading?.surveyId1 = Int32(autoIncrementId)
reading?.surveyId = Int32(item.SurveyId) ?? 0
reading?.readingData = item.CH4
reading?.surveyDateTime = item.SurveyReadingDateTime
reading?.latitude = item.Latitude
reading?.longitude = item.Longitude
reading?.serialNumber = item.SerialNumber
reading?.descriptionText = item.Description
reading?.componentName = item.Description
reading?.componentId = Int32(item.ComponentId) ?? 0
reading?.readingTypeId = Int32(item.ReadingTypeID) ?? 0
reading?.readingTypeDetails = item.ReadingTypeDetails
reading?.currentLatitude = item.CurrentLatitude
reading?.currentLongitude = item.CurrentLongitude
reading?.hdop = item.HDOP
reading?.vdop = item.VDOP
reading?.componentDistance = item.ComponentDistance
reading?.facilityId = Int32(item.ProjectId) ?? 0
reading?.posted = 1
reading?.readyToSync = 1
reading?.isComponentExists = DatabaseManager.sharedManager.getCompNameFromID(compID: Int32(item.ComponentId) ?? 0)
}
saveContext()
func getCompNameFromID(compID : Int32) -> Components? {
var component : Components? = nil
let fetchRequest : NSFetchRequest<Components> = Components.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "componentId == %ld", compID)
do {
let searchResults = try managedContext.fetch(fetchRequest)
component = (searchResults.first) ?? nil
} catch {
}
return component ?? nil
}
You can reduce the time dramatically if you add an NSCache instance to cache compID:Components key value pairs.
In getCompNameFromID check if the compID exists in the cache. If yes get the value from the cache (very fast), if not fetch the record and save it for the compID in the cache.
And use the convenient SurveyReadingWeb(context: initializer to get rid of all the ugly question marks, however the performance benefit is rather tiny.

Downcast from Any to Specific type

I have firestore DB "sales", which has one column called saleapproveddate. There are 2 levels of people, one who logs sale and other approves the sale logs. While logging sale, I save the saleapproveddate as NSNull() (which saves as nil in firestore DB field). Approver can update the saleapproveddate as TimeStamp, but if the approver never approves the sale log, it remains as nil in firestore DB field. So it can have either nil or TimeStamp type.
I have model Sales
class Sale {
var saleapprovedate : Any?
}
When I load the data from firestore, I tried to downcast the saleapprovedate as Any
let approvedDate = document[SaleProperties.paidDate.rawValue] as Any
But the real challenge is, saleapprovedate can have either nil or Timestamp. How do I check for type condition, convert to specific type and display in label?
Below is what I tried:
While loading data:
sale.saleapprovedate = document[SaleProperties.saleapprovedate.rawValue] as Any
while displaying data:
let saleItem = sales[indexPath.row]
let paidDate = saleItem.saleapprovedate
if paidDate == nil {
cell.paidDateLabelContainer.text = "Yet to pay"
cell.paidStatusImageView.isHidden = true
}
else {
let paidDateTimeStamp = saleItem.saleapprovedate as! Timestamp
let convertedPaidDate = self.convertTimestampToDate(timeStamp: paidDateTimeStamp)
cell.paidDateLabelContainer.text = convertDateToString(date: convertedPaidDate)
cell.paidStatusImageView.isHidden = false
}
But the above code is not updating the cell label properly. I have two data, one has saleapprovedate as Timestamp and other as nil. Both the cell label is displaying as "Yet to pay". What is wrong?
Modal :
var incentivepaiddate : Any?
Array :
var sales : [Sale] = [Sale]()
Loading data from firestore :
for document in querySnapshot!.documents {
let sale = Sale()
sale.incentivepaiddate = document[SaleProperties.incentivepaiddate.rawValue]
self.sales.append(sale)
}
Checking for nil, downcasting to a specific type and display data in cell
let saleItem = sales[indexPath.row]
let paidDate = saleItem.incentivepaiddate
if let paid = paidDate {
let paidDateTimeStamp = paid as? Timestamp
let convertedPaidDate = self.convertTimestampToDate(timeStamp: paidDateTimeStamp!)
cell.paidDateLabelContainer.text = convertDateToString(date: convertedPaidDate)
}
else {
cell.paidDateLabelContainer.text = "Yet to pay"
cell.paidStatusImageView.isHidden = true
}
Hope this helps someone!

SQLite.Swift how to get random row never repeating

I'm experimenting with SQlite.Swift in iOS and I'm trying to randomly pick a row from a table in an SQLite database.
The table is movies and its columns are movieID, title genre and seen.
Problem:
Movies should be picked randomly but as soon as I set seen to "yes" they should no longer be picked.
func randomRow() -> Int {
let path = NSSearchPathForDirectoriesInDomains(
.documentDirectory,
.userDomainMask, true)
.first!
let database = try! Connection("\(path)/movies.sqlite3")
var randomRow = Int(arc4random_uniform(UInt32(try!
database.scalar(movies.where(seen != "yes")
.select(movieID.distinct.count)))))
while previousNumber == randomRow {
randomRow = Int(arc4random_uniform(UInt32(try!
database.scalar(movies.where(seen != "yes")
.select(movieID.distinct.count)))))
}
previousNumber = randomRow
return randomRow
}
let destRow = randomRow()
Now I want to use this random row number to be catched from the table to lead the title and genre of the random movie into let ("dispMovie" and "movieGenre") to be output into a UITextField.
let path = NSSearchPathForDirectoriesInDomains(
.documentDirectory,
.userDomainMask, true)
.first!
let database = try! Connection("\(path)/filme.sqlite3")
for movie in try! database.prepare(
self.movies.where(
seen != "yes" && movieID == "\(destRow)")) {
dispMovie = movie[title]
movieGenre = movie[genre]
}
outputTextField.text = "\(dispMovie), \(movieGenre)"
This code works fine. The only problem is that movies I have seen will still be diplayed because I have this .count in the random row function.
I've also tried to call Raw SQLite with this one:
let newMovie = try! database.scalar(
"SELECT title, genre FROM movies WHERE seen != yes
ORDER BY RANDOM() LIMIT 1") as! String
outputTextField.text = newMovie
But this only displays the title and not the genre and looking for the genre in an extra line is not possible because the movie is picked from a random row. I've tried to reference the movie title in the Raw code but that crashes the app.
Thanks for help and hints.
No Christmas 'till this is done!
After days of thinking and running against walls suddenly the good old while loop came to my mind. It turned out working so I'm leaving this as an answer to my own question:
Forget about the randomRow(). No need for this. I focused on the SQLite RAW in the first place and continued with a combination of scalar, where and select from the SQLite.Swift library. That finally got me this:
Posting my whole UIViewController so you can test yourself:
class TestView: UIViewController {
//Table
let movies = Table("movies")
//Expressions
let movieId = Expression<String>("ID")
let movieTitle = Expression<String>("movieTitle")
let genre = Expression<String>("genre")
let seen = Expression<String>("seen")
//to find with database connection
var randomMovie = String()
var movieDetails = [String]()
//Array of all movies seen
var seenMovies = [String]()
//TestLabel to check everything
#IBOutlet weak var testLabel: UILabel!
#IBAction func testButton(_ sender: Any) {
//Open database connection
let path = NSSearchPathForDirectoriesInDomains(
.documentDirectory, .userDomainMask, true
).first!
let database = try! Connection("\(path)/movies.sqlite3")
//Check column "seen" and add all movie Titles to Array seenMovies
for movie in try! database.prepare(self.movies.where(seen == "yes")) {
self.seenMovies.append(movie[movieTitle])
}
//Select random row from movies Table with SQLite RAW as String
self.randomMovie = "\(try! database.scalar("SELECT movieTitle FROM movies WHERE seen != yes ORDER BY RANDOM() LIMIT 1"))"
//Create Array of details for that randomly picked movie
//Use scalar, where and select to find details from the picked movie above
self.movieDetails = ["\(randomMovie!)",
"\(try! database.scalar(movies.where(movieTitle == "\(randomMovie!)").select(genre)))"]
//And here's the "unique" thing...
//while randomMovie is also an element in seenMovies, loop until it's not
while seenMovies.contains("\(randomMovie!)") {
randomMovie = "\(try! database.scalar("SELECT movieTitle FROM movies WHERE seen != yes ORDER BY RANDOM() LIMIT 1"))"
}
//let testLabel show the results and see that no seen movie will be picked
testLabel.text = "\(details)\n\n\(erraten)"
//To prevent the Array from being appended over and over:
self.seenMovies.removeAll()
}
}
Since I still consider myself a beginner (started coding in October '17) please feel free to optimize this if possible. All my tests went very well and no movie was ever repeated when turned to "seen".
Found a solution from SQLite.Swift issue filed in 2016.
let db = Connection(databasePath)
let movies = Table("movies")
let query = movies.order(Expressions<Int>.random()).limit(1)
for row in try! db.prepare(query) {
// handle query result here
}

Adding Relational Data to a Class From User Class - Parse & Swift

I have to classes in parse, one is showing the users and the other is showing tweets. Both of the classes has a username column. However, I need to add a relational data to the tweets class that will include the info of the user class object for each user. I tried lots of codes but I could not make it. The views of my two classes are attached. How would be the swift code that when someone enters a new tweet a relational input will also be created in the tweet class. My current tweet code is as below:
var tweetObj = PFObject(className: "tweets")
tweetObj["userName"] = PFUser.currentUser()!.username
tweetObj["profileName"] = PFUser.currentUser()!.valueForKey("profileName") as! String
tweetObj["photo"] = PFUser.currentUser()!.valueForKey("photo") as! PFFile
tweetObj["tweet"] = theTweet
tweetObj["tweetType"] = 1
if hasImage == true {
tweetObj["hasImage"] = "yes"
let imageData = UIImagePNGRepresentation(self.tweetImg.image)
let imageFile = PFFile(name: "tweetPhoto.png", data: imageData)
tweetObj["tweetImage"] = imageFile
} else {
tweetObj["hasImage"] = "no"
}
tweetObj.save()
println("tweet!")
self.dismissViewControllerAnimated(true, completion: nil)
}
http://i.imgur.com/blszoqn.png
http://i.imgur.com/fK4ghlq.png

How to assign string to NSDictionary using for loop?

So, I retrieve song info using mpmediapicker on my application. I want to assign song info to dictionary so it's easy to populate it to tableview. But I found trouble when assigning value to dictionary on for loop.
Here is my code:
for thisItem in mediaItemCollection!.items as! [MPMediaItem]{
let itemUrl = thisItem.valueForProperty(MPMediaItemPropertyAssetURL)
as? NSURL
let itemTitle =
thisItem.valueForProperty(MPMediaItemPropertyTitle)
as? String
let itemArtist =
thisItem.valueForProperty(MPMediaItemPropertyArtist)
as? String
let itemArtwork =
thisItem.valueForProperty(MPMediaItemPropertyArtwork)
as? MPMediaItemArtwork
playlist = ["title" : itemTitle! , "artist" : itemArtist!, "song_url" : itemUrl!]
}
and below is what I got:
Optional({
artist = "ASKING ALEXANDRIA";
"song_url" = "ipod-library://item/item.mp3?id=5357233978197423496";
title = Alerion;
})
it's only retrieving the last song. How can I fix this problem ?
the format I want is like below:
Optional({artist = "ASKING ALEXANDRIA";"song_url" = "ipod-library://item/item.mp3?id=5357233978197423496";title = Alerion;}, {artist = "ASKING ALEXANDRIA";"song_url" = "ipod-library://item/item.mp3?id=5357239348197423496"; title = The Prophecy;}, {artist = "ASKING ALEXANDRIA";"song_url" = "ipod-library://item/item.mp3?id=53572339781974234123";title = Breathless;})
How can I do that?
If I am not wrong playlist is a NSDictionary
You have to create an NSMutableArray
var playlistArray : NSMutableArray = []
Now add the playlist in to array inside for loop.
playlistArray.addObject(playlist)
Print after completing the loop
println("\(playlistArray)")
You are not appending new data to the array you are just making the array equals the values
Try this:
playlist += ["title" : itemTitle! , "artist" : itemArtist!, "song_url" : itemUrl!]
or
playlist.append(["title" : itemTitle! , "artist" : itemArtist!, "song_url" : itemUrl!])
Assuming your playlist is an array of dictionaries that can be created like:
var playlist : [Dictionary<String, Int>] = []
or
var playlist = [Dictionary<String, Int>]()
or
var playlist : [[String:Int]] = []
or
var playlist = [[String:Int]]()

Resources