UIImage appears 2-3 seconds late on viewController swift - ios

I have a UIImageView that I use for users profile pic. But, the image appears late on screen like 2 to 3 seconds. Here is how I download it:
#IBOutlet weak var ProfilePic: UIImageView!
override func awakeFromNib() {
FriendSystem.system.USER_REF.child(user.uid).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.hasChild("imageUrl"){
self.firebaseStorage.child("users").child(FriendSystem.system.CURRENT_USER_ID).getData(maxSize: 10*1024*1024, completion: { (data, error) in
let userPhoto = UIImage(data: data!)
self.ProfilePic.image = userPhoto
})
}
})
}
How can I solve this?

The code you're running runs on a different Thread, and according to Apple, all UI updates must run on the Main Thread. Here's a link that explains why all UI updates must be on the Main Thread
So in your code you just have to use DispatchQueue like this
DispatchQueue.main.async {
// please don't use implicit unwrapping of variables `data!` cause it may cause crashes, use if let statements instead
if let data = data {
let userPhoto = UIImage(data: data)
self.ProfilePic.image = userPhoto
}
}
On a different note:
Please read on Swift coding standards in this link

Well i actually solved it and I will post it here if newbies like me encounter the same problem. I used Firebase and SDWebImage. Here is the link This is from Firebase's official website.
// Reference to an image file in Firebase Storage
let reference = storageRef.child("images/stars.jpg")
// UIImageView in your ViewController
let imageView: UIImageView = self.imageView
// Placeholder image
let placeholderImage = UIImage(named: "placeholder.jpg")
// Load the image using SDWebImage
imageView.sd_setImage(with: reference, placeholderImage: placeholderImage)

Related

Unable to reload collection view on successful image download

I'm editing a record which has some text information and images. As I get images URL so I'm using SDWebImage to download images which are then displayed in a collectionView and it's vertical scrollable. So when the view is loaded I'm doing this in viewDidLoad:
for (index, _) in mediaFiles.enumerated()
{
let img = UIImageView()
if let url = NSURL(string: "\(baseURLGeneral)\(mediaFiles[index].imageFile ?? "")")
{
print(url)
img.sd_setShowActivityIndicatorView(true)
img.sd_setIndicatorStyle(.gray)
img.sd_setImage(with: url as URL, placeholderImage: nil, options: .refreshCached, completed: { (loadedImage, error, cache, url) in
self.imagesArray.append(loadedImage!)
DispatchQueue.main.async
{
self.photoCollection.reloadData()
}
})
}
}
This code as per my understanding is downloading the image from web and when the image is loaded it add the image in an imageArray which I've declared as var imagesArray: [UIImage] = [] and then reload the collection view.
As this is the edit screen so user can also add more images in imagesArray which will show with the downloaded images in the same array and can also remove images.
As per collectionView delegate and dataSource is concerned so I'm returning return imagesArray.count in numberOfItemsInSection.
In cellForItemAt after making a cell variable I've cell.imageAddedByUser.image = imagesArray[indexPath.row].
THE ISSUE which I'm having is that after downloading images in viewDidLoad() collectionView is not getting refreshed. But If I pop and then push view controller it shows the images.
Try to call it in viewDidAppear()
DispatchQueue.main.async { self.photoCollection.reloadData() }
Try to dispatch it in main Queue
DispatchQueue.main.async
{
self.photoCollection.reloadData()
}

Sporadic behaviour in UITableViewCells when loading images

I have a Table View that presents comments. Each comment includes a label and a UIImage that presents the authors profile image. There is no pattern to the way images are appearing when the table is loaded. Sometimes they load fine. Other times, the placeholder image appears and the image never loads at all. If I scroll, the behavior varies. I'm using AlamofireImage.
After some research I came across this post where the recommendation is to call
self.tableView.beginUpdates()
self.tableView.endUpdates()
in AlamofireImage's completion handler. This had no effect at all.
Here is my original code:
func tableView(_ tableView: UITableView, cellForRowAt.....{
...
let myURL = Comment.profileImageURL
if Comment.profileImageURL != nil {
profileImage.af_setImage(withURL: myURL!, placeholderImage: #imageLiteral(resourceName: "Profile Image"))
} else {
profileImage.image = UIImage(named: "Profile Image")
}
And here is the update based on this question's recommendation (last answer on page):
let myURL = comment.profileImageURL
if comment.profileImageURL != nil {
cell.profileImage.af_setImage(
withURL: myURL!,
placeholderImage: #imageLiteral(resourceName: "Profile Image"),
filter: nil,
imageTransition: UIImageView.ImageTransition.crossDissolve(0.5),
runImageTransitionIfCached: false) {
// Completion closure
response in
// Check if the image isn't already cached
if response.response != nil {
// Force the cell update
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
}
} else {
cell.profileImage.image = UIImage(named: "Profile Image")
}
====EDIT====
Adding additional information in the hopes it will help:
I have tried setting the profileImage.image = nil as suggested below. I have also cancel the download in prepareForReuse() as suggested below to no avail.
Before calling the image, I am doing some configuration on the UIImageView
profileImage.layer.cornerRadius = profileImage.bounds.size.width / 2
profileImage.clipsToBounds = true
profileImage.layer.borderWidth = 2
profileImage.layer.borderColor = UIColor(red:222/255.0, green:225/255.0, blue:227/255.0, alpha: 1.0).cgColor
The following is the imageView setting in XCode:
Finally the issue itself. About 50% of the time I move back and forth between views or scroll, placeholder images appear in place of the actual image. In fact. The place holders often appear on first load.
===EDIT#2===
I'm trying to follow Ashley Mills second suggestion. I check the Image URL in the completion handler of AlamofireImage and the results are odd. I just loaded the table view. All images loaded fine as shown below:
I print out the the image URL when the Completion Handler fires and it looks fine:
I'm in completion
Optional(http://www.smarttapp.com/Portals/0/Users/018/18/18/sballmer.jpg)
I'm in completion
Optional(http://www.smarttapp.com/Portals/0/Users/018/18/18/sballmer.jpg)
I'm in completion
Optional(http://www.smarttapp.com/Portals/0/Users/011/11/11/Elon%20Musk.jpeg)
I'm in completion
Optional(http://www.smarttapp.com/Portals/_default/Users/001/01/1/Martin%20Profile.jpg)
Using the Navigation Bar, I back out, then reload and the images are all place holder images as shown:
The print out is as follows:
I'm in completion nil
I'm in completion nil
I'm in completion nil
Finally this is the code where the image is loaded and the completion handler:
let myURL = Comment.profileImageURL
if Comment.profileImageURL != nil {
// profileImage.af_setImage(withURL: myURL!, placeholderImage: #imageLiteral(resourceName: "Profile Image"))
profileImage.af_setImage(
withURL: myURL!,
placeholderImage: #imageLiteral(resourceName: "Profile Image"),
filter: nil,
completion:{ image in
print("I'm in completion")
print(image.response?.url)
self.setNeedsLayout()
self.layoutIfNeeded()
})
} else {
profileImage.image = UIImage(named: "Profile Image")
}
I've tried everything at this point. Please help.
UITableViewController reuses cells. In order to prevent old images reappearing in cells that are not fully loaded yet, set the cell's image to nil before loading.
cell.profileImage.image = nil
let myURL = comment.profileImageURL
if comment.profileImageURL != nil {
[...]
} else {
cell.profileImage.image = UIImage(named: "Profile Image")
}
Each cell you display in image in can be reused if is scrolled off the screen. Using af_setImage will fetch the image, and display it when the response is received, but by that time the cell might be reused, and be trying to display an image at a different URL.
You can handle this in a couple of ways, but first thing to do is to add a Comment property to your cell, and handle the downloading there rather than in the view controller (you might find making all your IBOutlet properties fileprivate helps with this - it makes sure you're can't update the cell's UI from the view controller)
1) Cancel the download request when the cell is reused…
override func prepareForResuse() {
profileImage.af_cancelImageRequest
}
This should prevent the response for a previous URL being returned, but will mean the image isn't downloaded.
2) Check the URL that that the response is for is the one you're expecting.
var requestedURL: URL?
var comment: Comment? {
didSet {
if let myURL = Comment.profileImageURL {
requestedURL = myURL
// Fetch image from URL and when response is received,
// check self.myURL == response.url
} else {
profileImage.image = UIImage(named: "Profile Image")
}
}
}
This way the image will still be downloaded even if it's not displayed.

Swift 3 Photo Share Extension with Firebase Database Not Working After First Send

I am developing an iOS photo sharing extension in Swift 3 that captures a user-selected photo in the iOS Photos app along with a user-entered caption and stores it in Firebase. The photo is stored in Firebase storage. The caption and the path of the photo in Firebase Storage are stored in Firebase Realtime Database.
The problem I'm encountering is that the share extension stops working after first send. The strange thing is, if I do a similar approach in a regular View Controller in the iOS app, the code works. I noticed two issues with the Share Extension:
Issue #1: The share extension view isn't dismissed completely. In a normal situation, the view would return to a "gallery" mode of Photos. However, the share extension view is going away but the share menu with the complete list of apps that you can use to share is not dismissing.
Screenshot of what the Photos view looks like after the second send
Issue #2: the data isn't being sent up to Firebase storage or Firebase database.
Below please find my ShareViewController code:
import UIKit
import Social
import Firebase
import MobileCoreServices
class ShareViewController: SLComposeServiceViewController {
var ref: DatabaseReference!
var storageRef: StorageReference!
override func isContentValid() -> Bool {
// Do validation of contentText and/or NSExtensionContext attachments here
return true
}
override func didSelectPost() {
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
FirebaseApp.configure()
ref = Database.database().reference()
storageRef = Storage.storage().reference()
// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
if let item = self.extensionContext?.inputItems[0] as? NSExtensionItem {
for ele in item.attachments!{
let itemProvider = ele as! NSItemProvider
if itemProvider.hasItemConformingToTypeIdentifier("public.jpeg"){
itemProvider.loadItem(forTypeIdentifier: "public.jpeg", options: nil, completionHandler: { (item, error) in
do {
var imgData: Data!
if let url = item as? URL{
imgData = try Data(contentsOf: url)
}
if let img = item as? UIImage{
imgData = UIImagePNGRepresentation(img)
}
var updateRef = self.ref.child("demo_group").child("demo_patient").child("demo_updates").childByAutoId()
var updateStorageRef = self.storageRef.child("demo_photos" + "/\(Double(Date.timeIntervalSinceReferenceDate * 1000)).jpg")
updateRef.child("event_name").setValue(self.contentText)
updateRef.child("sender").setValue("demo_ff")
let metadata = StorageMetadata()
metadata.contentType = "image/jpeg"
updateStorageRef.putData(imgData, metadata: metadata) { (metadata, error) in
if let error = error {
print("Error uploading: \(error)")
return
}
// use sendMessage to add imageURL to database
updateRef.child("photos").childByAutoId().setValue(metadata?.path)
}
} catch let err{
print(err)
}
})
}
}
}
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
override func configurationItems() -> [Any]! {
// To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
return []
}
}
Please let me know if there's anything I can do to fix this! I would be greatly appreciated.
FirebaseApp.configure() will crash your extension if call multiple times in a row because the system does not fully deallocate the firebase app instance created for the extension.
My solution was to do:
if let _ = FirebaseApp.app() {...} else {
FirebaseApp.configure()
}
This should fix your problem.
Edit: Also note that you should call extensionContext.complete after your storage & database updates have completed... Not before
So, you will need to use the methods for setValue that take a completion block.
1- Upload to storage, when the task completes
2- Update the database reference,
3- When that completion block is called... Then you call extensionContext.completeWith() or .cancel()

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).

Photos framework: Connection to assetsd was interrupted or assetsd died

I am getting this error when I am trying to play multiple videos using this swift library (https://github.com/piemonte/player). Not sure if it's related to that player, or to the Photos framework or what though.
What happens is, I have a view that will display either a photo or a video. Everything works fine a few times until a few videos have played and then this message will pop up, followed by all the videos not being able to play and in their place you just see a black screen, and then I get a memory usage error.
I am using a library called SwipeView and here is some relevant code which may be helpful.
func swipeView(swipeView: SwipeView!, viewForItemAtIndex index: Int, reusingView view: UIView!) -> UIView! {
let asset: PHAsset = self.photosAsset[index] as PHAsset
// Create options for retrieving image (Degrades quality if using .Fast)
// let imageOptions = PHImageRequestOptions()
// imageOptions.resizeMode = PHImageRequestOptionsResizeMode.Fast
var imageView: UIImageView!
let screenSize: CGSize = UIScreen.mainScreen().bounds.size
let targetSize = CGSizeMake(screenSize.width, screenSize.height)
var options = PHImageRequestOptions()
options.resizeMode = PHImageRequestOptionsResizeMode.Exact
options.synchronous = true
if (asset.mediaType == PHAssetMediaType.Image) {
PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: targetSize, contentMode: .AspectFill, options: options, resultHandler: {(result, info) in
if (result.size.width > 200) {
imageView = UIImageView(image: result)
}
})
return imageView
} else if (asset.mediaType == PHAssetMediaType.Video) {
self.currentlyPlaying = Player()
PHImageManager.defaultManager().requestAVAssetForVideo(asset, options: nil, resultHandler: {result, audio, info in
self.currentlyPlaying.delegate = self
self.currentlyPlaying.playbackLoops = true
self.addChildViewController(self.currentlyPlaying)
self.currentlyPlaying.didMoveToParentViewController(self)
var t = result as AVURLAsset
var url = t.valueForKey("URL") as NSURL
var urlString = url.absoluteString
self.currentlyPlaying.path = urlString
})
return self.currentlyPlaying.view
}
return UIView()
}
func swipeViewItemSize(swipeView: SwipeView!) -> CGSize {
return self.swipeView.bounds.size;
}
func swipeView(swipeView: SwipeView!, didSelectItemAtIndex index: Int) {
self.currentlyPlaying.playFromBeginning()
}
func swipeViewCurrentItemIndexDidChange(swipeView: SwipeView!) {
self.currentlyPlaying.stop()
}
Any thoughts would be great.
I had this same "Connection to assetsd was interrupted or assetsd died" error.
Usually followed by a memory warning.
I tried to find retain cycles, but it wasn't it.
The problem for me had to do with the known fact that the resultHandler block of any PHImageManager request...() is not called on the main queue.
So we cannot run UIView code directly within those blocks without asking for trouble later on in the app life.
To fix this we may for instance run all UIView code that would be executed within the scope of the resultHandler block inside a dispatch_async(dispatch_get_main_queue(), block)
I was having this problem because I did not realize that one of the functions that I was calling within one of the resultHandler.s of my app did eventually call some UIView code down the line.
And so I was not forwarding that call to the main thread.
Hope this helps.
The issue is occurring because of the TargetSize only. The PHImageManagerMaximumSize option is the culprit. Set the CGSIZE as you want for the imageview.
Your problem might be that you create a new Player-object everytime you start playing a video:
self.currentlyPlaying = Player()
I would recommend you to either remove it after the file has finished playing or to create one player which you will reuse. Otherwise it will stay in the memory.v

Resources