Parse in swift is very slow - my programming at fault ? - ios

I have a main class in Parse, which feeds a table view in swift. the background image covers the whole of the background. These images are 480k in size. They do not always turn up on the tableviewcell (I know I can add an placeholder image). I have a very faster connection to my mobile.. If i use 4g they don't even turn up at all.
Here is my function for pulling the images down. I don't really know what else I can do.
func loadImages() {
var query = PFQuery(className: "TableViewData")
query.orderByDescending("objectId")
query.findObjectsInBackgroundWithBlock ({(objects:[AnyObject]!, error: NSError!) in
if(error == nil){
let imageObjects = objects as [PFObject]
for object in objects {
let thumbNail = object["backgroundImage"] as PFFile
thumbNail.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) -> Void in
if (error == nil) {
let image = UIImage(data:imageData)
self.CellBackgroundImage.append(image!)
println(self.CellBackgroundImage.count)
}
})
}
}
else{
println("Error in retrieving \(error)")
}
})
}
As you can see I add them as a an array and the tableview will show them according to the index.path.
Can anyone suggest anything ?

Updates to the user interface must always be performed on the main thread. Asynchronous networking completions are typically called on a background thread (not sure about PFQuery stuff), so you need to force the update to happen on the correct thread:
dispatch_async(dispatch_get_main_queue()) {
self.CellBackgroundImage.append(image!)
}

Related

Best practice for navigating to new view controller while still waiting for data

Right now I have a tableviewcontroller. When I click on one of the cells, I segue to a new viewcontroller that displays details about the item I clicked on.
Sometimes not all of the info I need has been loaded yet (especially on slow connections), so I'm trying to load it in the background even after the segue. I know I could just freeze the main thread and wait for my data to load, but I'd rather have a fluent interface.
For example, if a user's profile picture isn't loaded yet, then a placeholder image is shown and replaced when the real one loads in. I can't seem to get this behavior to work though. Here's what I'm doing right now:
func onItemTap(send:AnyObject){
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc: OtherViewController = storyboard.instantiateViewControllerWithIdentifier("OtherViewController") as! OtherViewController
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), {
while(self.parseUser == nil){//wait for it to load}
let profImage = (self.parseUser?.valueForKey("profilePictureLarge") as! PFFile)
profImage.getDataInBackgroundWithBlock({
(imageData: NSData?, error: NSError?) -> Void in
if (error == nil) && vc.profileImageView != nil{
vc.profileImageView.image = UIImage(data: imageData!)
vc.userNameLabel.text = self.parseUser?.valueForKey("displayName") as? String
vc.parseUser = self.parseUser
vc.pfObject = self.parseObject
}
})
})
//these are sent no matter what, but are replaced by background thread if needed
vc.parseUser = self.parseUser
vc.pfObject = self.parseObject
holder?.navigationController?.pushViewController(vc, animated: true)
}
I'm not sure what I should really be doing. This seems to work in some cases, but just not at all most of the time.
How can I asynchronously send data that is being loaded somewhere on a background thread and have it reliably show up when I need it?
Move your dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0) block to viewWillAppear() of view controller being pushed.
Actually you don't need to use dispatch_async since you're already calling asynchronous PFQuery. So your viewcontroller's viewWillAppear() method will look like this:
override func viewWillAppear(animated: Bool){
self.userNameLabel.text = self.parseUser?.valueForKey("displayName") as? String
let profImage = (self.parseUser?.valueForKey("profilePictureLarge") as! PFFile)
profImage.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) in
if (error == nil) && self.profileImageView != nil{
self.profileImageView.image = UIImage(data: imageData!)
}
})
}
Additionally, if you could, take a look into PFImageView documentation (it is inside ParseUI.framework).
It makes setting images from PFFile much easier.

Swift 2.0 Parse PFFile/Image

I have a Swift project that uses Parse to store profile pics. For some reason the PFFile profile image was a pain to get working. I finally got it working in Swift 1.2 with this function:
func image(completion: (image: UIImage) -> Void)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
if self.profilePictureImage == nil
{
if self.profilePicture != nil
{
self.fetchIfNeeded()
if let data = self.profilePicture!.getData()
{
self.profilePictureImage = UIImage(data: data)
}
}else
{
self.profilePictureImage = UIImage(named: "no_photo")!
}
}
dispatch_async(dispatch_get_main_queue(),{
completion(image: self.profilePictureImage)
})
})
}
profilePicture is the #NSManaged PFFile
profilePictureImage' is aninternal UIImage`
I've migrated the project to Swift 2.0 and it's crashing with an unwrapped nil error on the completion call.
What's changed? How can I address this? Thanks!
First off, check out the ParseUI framework which includes the PFImageView class for automatically handling the downloading and displaying of PFFiles.
Create the outlet
#IBOutlet weak var profilePictureImage: PFImageView!
Typical usage
// Set placeholder image
profilePictureImage.image = UIImage(named: "no_photo")
// Set remote image (PFFile)
profilePictureImage.file = profilePicture
// Once the download completes, the remote image will be displayed
profilePictureImage.loadInBackground { (image: UIImage?, error: NSError?) -> Void in
if (error != nil) {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
} else {
// profile picture loaded
}
}
Outside of that, there have been a bunch of posts lately with people experiencing issues with PFFile.getData() not working in Swift2 due to the changes in iOS9 to app transport security. According to Parse this has been fixed in the latest SDK

very weird Xcode project bug?

I am attempting to integrate some JSSAlertViews within one of my ViewControllers, but for some odd reason, when I run my project, the alert views do not show. So to make sure it wasn't any error with coding, I created an exact pseudo project to replicate the ViewController of my original project, down to it's UI elements on the storyboard. I copied the exact code from my original project onto the new ViewController, ran it, and everything worked. Im stuck onto figuring out, why won't it work on my original project??
here is the logic i used:
#IBAction func resetPass(sender: AnyObject) {
actview.hidden = false
actview.startAnimating()
PFUser.requestPasswordResetForEmailInBackground(emailReset.text) {
(success:Bool, error:NSError?) ->Void in
if(success){
let yesMessage = "Email was sent to you at \(self.emailReset.text)"
dispatch_async(dispatch_get_main_queue()) {
self.actview.stopAnimating()
JSSAlertView().success(self, title:"Great Success", text:yesMessage)
}
}
if(error != nil){
let errorMessage:String = error!.userInfo!["error"] as! String
dispatch_async(dispatch_get_main_queue()) {
self.actview.stopAnimating()
JSSAlertView().warning(self, title:"Woah There", text:errorMessage)
}
}
}
}
I set a breakpoint on a call of one of the JSSAlertView's , expanded the element in my console and got this :
Is this a memory management error and reason why they aren't visible? how do i fix this?
here is the Git if you want to check it out, its awesome: https://github.com/stakes/JSSAlertView
Anything with the UI needs to be done on the main thread, and you're calling the Parse function on the background thread (via requestPasswordResetForEmailInBackground).
So to get your alerts to appear on the main thread, you need to add a little GCD magic:
#IBAction func resetPass(sender: AnyObject) {
actview.hidden = false
actview.startAnimating()
PFUser.requestPasswordResetForEmailInBackground(emailReset.text) {
(success:Bool, error:NSError?) ->Void in
if(success){
self.actview.stopAnimating()
let yesMessage = "Email was sent to you at \(self.emailReset.text)"
dispatch_async(dispatch_get_main_queue()) {
self.successMessage(yesMessage)
}
};
if(error != nil){
self.actview.stopAnimating()
let errorMessage:String = error!.userInfo!["error"] as! String
dispatch_async(dispatch_get_main_queue()) {
self.failureMessage(errorMessage)
}
}
}
}
See my addition of the "dispatch_async(dispatch_get_main_queue())" lines?

Parse.com Multiple Users accessing data - Swift

I am making a to do app using parse.com. I have got data which is set by a user and they should be able to assign that data to another user too. So the user sets a title, text and the name of the user they wish to assign it to. All of this is stored in the database and works well. When the same user logs in the data they set is displayed for him in the table view controller.
However the user they assigned to see the data does not display. So let me explain with code. The code underneath allows the user to see the data they set when they go to Table view screen. The code under is in the viewDidAppear function.
func fetchAllObjectsFromLocalDatastore() {
var query: PFQuery = PFQuery(className: "toDo")
query.fromLocalDatastore()
query.whereKey("username", equalTo: PFUser.currentUser().username)
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if (error == nil) {
var temp: NSArray = objects as NSArray
self.toDoObjects = temp.mutableCopy() as NSMutableArray
self.tableView.reloadData()
}else {
println(error.userInfo)
}
}
With this, I have another function:
func fetchAllObjects() {
PFObject.unpinAllObjectsInBackgroundWithBlock(nil)
var query: PFQuery = PFQuery(className: "toDo")
query.whereKey("username", equalTo: PFUser.currentUser().username)
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if (error == nil) {
PFObject.pinAllInBackground(objects, block: nil)
self.fetchAllObjectsFromLocalDatastore()
}else {
println(error.userInfo)
}
}
}
The user he wants to see that data to is assigned in the add new to do screen and that works. It even displays on the Parse.com database. But how can I query it or get it to show that data for the user who the data has been assigned to not just the user who set it.
So when (for example) UserOne sets data, it appears for him when he logs in and goes to table view, but even when he assigns to (for example) UserTwo and UserTwo logs in and goes to table view where the data is meant to be, I just do not know HOW TO DO??!
Please do help me or give me guidance, I am still searching on the solution to this, I feel like it is really simple but I cannot put my finger on it.
UPDATE *2
When the data is set, it is set with who the UserOne would like to give access to the data to.
So here is the saving process I guess:
#IBAction func saveAction(sender: UIBarButtonItem) {
self.object["username"] = PFUser.currentUser().username
self.object["title"] = self.titleField?.text
self.object["text"] = self.textView?.text
self.object["forUser"] = self.userToAssignTo?.text
self.object.saveEventually { (success, error) -> Void in
if (error == nil) {
}else{
println(error.userInfo)
}
}
So then I tried to query the forUser:
func fetchAllObjects() {
PFObject.unpinAllObjectsInBackgroundWithBlock(nil)
var query: PFQuery = PFQuery(className: "toDo")
query.whereKey("forUser", equalTo: PFUser.currentUser().username)
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if (error == nil) {
PFObject.pinAllInBackground(objects, block: nil)
self.fetchAllObjectsFromLocalDatastore()
}else {
println(error.userInfo)
}
}
}
This still did not seem to work. I'm not sure what I'm doing now! Damn I just don't know how I can let another user see the data that one user has set.
UPDATE 3**
I found it! It was all about the viewDidAppear function, so here is the code:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if (PFUser.currentUser() == nil) {
var logInViewController = PFLogInViewController()
logInViewController.delegate = self
var signUpViewController = PFSignUpViewController()
signUpViewController.delegate = self
logInViewController.signUpController = signUpViewController
self.presentViewController(logInViewController, animated: true, completion: nil)
}else {
//self.fetchAllObjectsFromLocalDatastore()
//self.fetchAllObjects()
self.fetchAllObjectsFromLocalDatastore2()
self.fetchAllObjects2()
}
}
It is basically because the fetchAllObjects was kind of wiping it out for the "forUser" key. But now I need to figure out how to do it so the fetchAllObjects don't wipe each other off because it almost like a refresh button and then all the data is wiped off the screen.
I have been working on a similar issue however with push notifications from parse. I can provide code examples later today. There are a couple ways to approach. When the user creates a new to do you can set a custom object with the user they want to assign to and you can query based on the field. You can save the to do with saveinbackground with block. Another way is to use PFRelation. Watched a few courses on team tree house to review and see different techniques and they have one that is a self destructing messaging app that uses parse for functions much similar to what you want to do.

Loading images from Parse using Swift

I've written my code for retrieving images from my Parse data console so that they will appear in my Storyboard for my Swift app, but I'm not sure how to make an IBOutlet connect to the UIImageView I added to my Storyboard so that the image from Parse will actually appear in its place. Here's the code I have so far in my ViewController:
var imageResources : Array<UIImage> = []
override func viewDidLoad() {
super.viewDidLoad()
func loadImages(){
var query2 = PFQuery(className: "VoteCount")
query.findObjectsInBackgroundWithBlock ({(objects:[AnyObject]!, error: NSError!) in
if(error == nil){
let imageObjects = objects as [PFObject]
for object in objects {
let thumbNail = voteCount1["image"] as PFFile
thumbNail.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) -> Void in
if (error == nil) {
let image = UIImage(data:imageData)
self.imageResources.append(image!)
println(image)
}
})
}
}
else{
println("Error in retrieving \(error)")
}
})
}
}
What kind of IBOutlet do I make that connects to the UIImageView I've added to my Main.storyboard? I'm just not sure what to name it that will call the image from Parse that I'm trying to retrieve.
control-drag from your UIImageView to your Swift file, outside the methods but inside the implementation. name it myImageView.
You can't call println on an image.
Then after the line if (error == nil) { try the following
if let image = UIImage(data:imageData) {
dispatch_async(dispatch_get_main_queue()) {
self.myImageView.image = image
}
}
Or something to that effect. You need to be sure:
the image exists
you do anything with UI updates on the main thread (that's what dispatch_async(dispatch_get_main_queue()) {} accomplishes
Try it and let me know if that helps. I'm on the road right now but when I get a chance to sit I'll circle back and check this out.
One last trick: you can put a breakpoint on lines where you get an image. Then when the application pauses, you can hover over the word image in the line if let image = UIImage(data:imageData) { and click on the eye icon in the popup. You should then be able to view the image from Parse. If the image doesn't appear when you click the eye icon, you probably have additional debugging to do (does the image exist, is your logic up to that point correct, etc).

Resources