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).
Related
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.
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
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?
I have a header view for every UITableViewCell. In this header view, I load a picture of an individual via an asynchronous function in the Facebook API. However, because the function is asynchronous, I believe the function is called multiple times over and over again, causing the image to flicker constantly. I would imagine a fix to this issue would be to load the images in viewDidLoad in an array first, then display the array contents in the header view of the UITableViewCell. However, I am having trouble implementing this because of the asynchronous nature of the function: I can't seem to grab every photo, and then continue on with my program. Here is my attempt:
//Function to get a user's profile picture
func getProfilePicture(completion: (result: Bool, image: UIImage?) -> Void){
// Get user profile pic
let url = NSURL(string: "https://graph.facebook.com/1234567890/picture?type=large")
let urlRequest = NSURLRequest(URL: url!)
//Asynchronous request to display image
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue()) { (response:NSURLResponse!, data:NSData!, error:NSError!) -> Void in
if error != nil{
println("Error: \(error)")
}
// Display the image
let image = UIImage(data: data)
if(image != nil){
completion(result: true, image: image)
}
}
}
override func viewDidLoad() {
self.getProfilePicture { (result, image) -> Void in
if(result == true){
println("Loading Photo")
self.creatorImages.append(image!)
}
else{
println("False")
}
}
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//Show section header cell with image
var cellIdentifier = "SectionHeaderCell"
var headerView = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! SectionHeaderCell
headerView.headerImage.image = self.creatorImages[section]
headerView.headerImage.clipsToBounds = true
headerView.headerImage.layer.cornerRadius = headerView.headerImage.frame.size.width / 2
return headerView
}
As seen by the program above, I the global array that I created called self.creatorImages which holds the array of images I grab from the Facebook API is always empty and I need to "wait" for all the pictures to populate the array before actually using it. I'm not sure how to accomplish this because I did try a completion handler in my getProfilePicture function but that didn't seem to help and that is one way I have learned to deal with asynchronous functions. Any other ideas? Thanks!
I had the same problem but mine was in Objective-C
Well, the structure is not that different, what i did was adding condition with:
headerView.headerImage.image
Here's an improved solution that i think suits your implementation..
since you placed self.getProfilePicture inside viewDidLoad it will only be called once section==0 will only contain an image,
the code below will request for addition image if self.creatorImages's index is out of range/bounds
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//Show section header cell with image
var cellIdentifier = "SectionHeaderCell"
var headerView = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! SectionHeaderCell
if (section < self.creatorImages.count) // validate self.creatorImages index to prevent 'Array index out of range' error
{
if (headerView.headerImage.image == nil) // prevents the blinks
{
headerView.headerImage.image = self.creatorImages[section];
}
}
else // requests for additional image at section
{
// this will be called more than expected because of tableView.reloadData()
println("Loading Photo")
self.getProfilePicture { (result, image) -> Void in
if(result == true) {
//simply appending will do the work but i suggest something like:
if (self.creatorImages.count <= section)
{
self.creatorImages.append(image!)
tableView.reloadData()
println("self.creatorImages.count \(self.creatorImages.count)")
}
//that will prevent appending excessively to data source
}
else{
println("Error loading image")
}
}
}
headerView.headerImage.clipsToBounds = true
headerView.headerImage.layer.cornerRadius = headerView.headerImage.frame.size.width / 2
return headerView
}
You sure have different implementation from what i have in mind, but codes in edit history is not in vain, right?.. hahahaha.. ;)
Hope i've helped you.. Cheers!
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!)
}