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
Related
I'm trying out the Snap Kit Framework from Snapchat using their sample code but when running the code nothing happens and no error is thrown.
import SCSDKCreativeKit
let snap = SCSDKNoSnapContent()
snap.sticker = SCSDKSnapSticker(stickerImage: UIImage(named: "story_share.png")!)
snap.caption = "Snap on Snapchat!"
snap.attachmentUrl = profileURL.absoluteString
SCSDKSnapAPI().startSending(snap) { (error: Error?) in
print(error)
}
The logs don't indicate an error either:
myapp[853:121028] [SnapKit] Dynamic config update status: success
Things I've tried or insured are correct:
My app is registered online with the correct bundle id, my test snapchat username and the creative kit is selected
the development key is in the Info.plist file as SCSDKClientId
snapchat is in the list of LSApplicationQueriesSchemes
the code runs on the main thread
using their userInteractionEnabled trick doesn't make a difference
I'm using carthage and embed the core and creative framework as usual
What could be the reason why this doesn't work?
I found the disappointing answer that it only works using the deprecated method call:
SCSDKSnapAPI(content: snap).startSnapping() { (error: Error?) in
...
}
I hope that Snapchat fixes their terrible framework in the future. If anyone knows an actual solution, please let me know and I'll accept your answer.
SCSDKSnapAPI() needs to be defined as a class variable. Your code doesn't show the implementation, but I had the same issue when I instantiated SCSDKSnapAPI() in a func() instead of at the Class level.
Here's an implementation that I found which works as intended:
class CreateViewController: UIViewController {
fileprivate lazy var snapAPI = {
return SCSDKSnapAPI()
}()
#IBAction func sendSnap2(_ sender: Any) {
//self.snapAPI = SCSDKSnapAPI() <<DO NOT DO THIS>>
let snap2 = SCSDKNoSnapContent()
view.isUserInteractionEnabled = false
snapAPI.startSending(snap) { [weak self] (error: Error?) in
self?.view.isUserInteractionEnabled = true
if (error != nil) {
print("Error Unknown", error!)
}
else {
print("no error")
}
}
}
I am a new developer. I'm using Swift 4.2 and Xcode 10.2.
I'm trying to show a spinner while a photo is being saved. I am simulating a slow connection on my iPhone in Developer Mode to test it. Using the code below the spinner does not show up. The view goes right to the end where the button shows and says the upload is complete (when it isn't). I tried putting it all into a DispatchQueue.global(qos: .userinitiated).async and then showing the button back on the main queue. I also tried putting the showSpinner on a DispatchQueue.main and then savePhoto on a .global(qos: .utility). But I cleary don't understand the GCD processes.
Here's my code:
func savePhoto(image:UIImage) {
// Add a spinner (from the Extensions)
self.showSpinner(onView: self.view)
PhotoService.savePhoto(image: image) { (pct) in
// Can show the loading bar here.
}
// Stop the spinner
self.removeSpinner()
// Show the button.
self.goToPhotosButtonLabel.alpha = 1
self.doneLabel.alpha = 1
}
What types of DispatchQueues should I use and where should I put them?
Here is the savePhoto code:
static func savePhoto(image:UIImage, progressUpdate: #escaping (Double) -> Void) {
// Get data representation of the image
let photoData = image.jpegData(compressionQuality:0.1)
guard photoData != nil else {
print("Couldn't turn the image into data")
return
}
// Get a storage reference
let userid = LocalStorageService.loadCurrentUser()?.userId
let filename = UUID().uuidString
let ref = Storage.storage().reference().child("images/\(String(describing: userid))/\(filename).jpg")
// Upload the photo
let uploadTask = ref.putData(photoData!, metadata: nil) { (metadata, error) in
if error != nil {
// An error during upload occurred
print("There was an error during upload")
}
else {
// Upload was successful, now create a database entry
self.createPhotoDatabaseEntry(ref: ref, filename: filename)
}
}
uploadTask.observe(.progress) { (snapshot) in
let percentage:Double = Double(snapshot.progress!.completedUnitCount /
snapshot.progress!.totalUnitCount) * 100.00
progressUpdate(percentage)
}
}
Since the code that saves the photo is asynchronous , so your current code removes the spinner directly after it's added before the upload is complete
func savePhoto(image:UIImage) {
// Add a spinner (from the Extensions)
self.showSpinner(onView: self.view)
PhotoService.savePhoto(image: image) { (pct) in
// remove spinner when progress is 100.0 = upload complete .
if pct == 100.0 {
// Stop the spinner
self.removeSpinner()
// Show the button.
self.goToPhotosButtonLabel.alpha = 1
self.doneLabel.alpha = 1
}
}
}
Here you don't have to use GCD as firebase upload runs in another background thread so it won't block the main thread
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'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).
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!)
}