CustomImageView and Post Ordering Fixes
Instagram Firebase
Let's take a quick little breather from feature implementations to fix a few bugs we introduced in the last episode. While we are now able to post photos, we have a few problems rendering them correctly inside of our UserProfileController. I'll first go over what the issues are regarding the UICollectionView.reloadData() method. Next we dive into the proper fix of url checking in our image loading process. Finally, I'll go into the fix of ordering our Posts as well as reloading our list every time a new post is uploaded.

Comments (30)
juliolocoh
6 years ago
Hi Brian can you make all the videos of your courses to download?
Brian Voong
6 years ago
Jesus Adolfo
6 years ago
I thought the same but it is obvious you want to protect your content. I guess one solution would be to make and app that allows downloads within the app. Something like the Netflix app where you can download content but only watch it in it. But then it should probably be a Mac App since most of us watch the videos from our Macs when following the tutorials... Idk just a thought. Easier said done.
Fred van Rijswijk
6 years ago
How to show an other view under the grid view if a User has no photo's yet?
Dax Rahusen
6 years ago
Check this out, works fine with me. https://github.com/dzenbot/DZNEmptyDataSet
Dax Rahusen
6 years ago
To do it without any third party, check if the datasnapshot value has no children and render a custom Cell instead.
Fred van Rijswijk
6 years ago
Thnx
Fred van Rijswijk
6 years ago
Question is where to do that, in viewDidLoad?
Fred van Rijswijk
6 years ago
Getting this error also : 'NSInvalidArgumentException', reason: 'UICollectionView must be initialized with a non-nil layout parameter' but is set: func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: view.frame.width, height: 100) }
Brian Voong
6 years ago
Fred van Rijswijk
6 years ago
CustomImageView is caching, but with as lot of images I see still a big amount of data in Network... I now use SDWebImage for loading the ImageView instead of CustomImageView I can use UIImage and I can get the photo from Firebase in one line of code ;-) profileImageView.sd_setImage(with: URL(string: profileImageUrl), placeholderImage: UIImage(named: "defaultImagePreview"))
Brian Voong
6 years ago
Maher M Al Demerdash
6 years ago
I just spent the last 1-2 hours trying to figure out why the heck my collectionView is not updating after I add a new photo... It turned out that I called the reload method AFTER the observe block curly braces heheheheh a stupid mistake :S
Brian Voong
6 years ago
Maciek Górecki
6 years ago
Brian, Do you maybe have an idea why my ordering isn't working? I have the proper code ref.queryOrdered(byChild: "creationDate").observe(.childAdded, with: { (snapshot) in... I even copied your code, also I don't have any mistypes in firebase either. The nodes are correct like: creationDate: 1493334626.666354 Can't figure out what's going on. The cells are still appearing in the wrong order.
Maciek Górecki
6 years ago
Now I see that indeed it's working but I had to change the creationDate to a negative value to start sorting properly (newest at the top). But still when I upload a photo and the collectionView updates, the photo appears at the bottom. When I launch the app again, it's at the correct location (first at the top)
Maciek Górecki
6 years ago
OK, I figured it out. I changed posts.append(post) to posts.insert(post, at: 0) in fetchOrderedPosts and everything works perfectly now :)
josephlausf
6 years ago
It's indeed sorted in the right order, but it seems like Firebase only returns the posts in ascending order. You can insert each post to index-0 of your posts array on your end. Like you, I also thought that queryOrdered was supposed to reverse the order with most recent first.
mattbecker7
5 years ago
you can change self.posts.append(post) to self.posts.insert(post, at: 0) you will still need to make sure firebase is ordered the query before inserting i think
Jesus Adolfo
6 years ago
awesome awesome! Thank you
josephlausf
6 years ago
Hi Brian, amazing course! Can't tell you how grateful I am to have you as my instructor. Loved every single lesson so far! Worth every penny and more! I do have a question regarding code organization. I was wondering what's best practice -- putting fetchUser() and fetchPosts() implementations in a global "DataService" class or in their associated classes like User.fetchUser() and Post.fetchPosts(), or is there a more proper way? Thanks!
Brian Voong
6 years ago
victor
6 years ago
Hey Brian, I love this course!!! one small bug which I can't fix. Even after implementing this func loadImage(urlString: String) { ... if url.absoluteString != self.lastURLUsedToLoadImage { return ...} inside CustomerImageView, my post photos still repeat. Tested with two similar photo posts: the FIR database showed unique uids for both posts, and FIR storage showed unique URLs for both photos. So not sure why the bug fix above doesn't work before and after the refactoring to CustomImageView. Can't wrap my head around why... Thanks!
Brian Voong
6 years ago
victor
6 years ago
Hey Brian, I think my app is in a bad state when I fire load images. I apologise for the long reply here. I tried your code and photos still repeated so I went back to break points in fetchUser() and fetchPosts() and discovered errors which I didn't notice earlier. 1) Fire fetchUser() and console shows uid unable to read data i.e. user is nil. which is odd. Error reads: firebaseInstagram[4183:106220] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9806) 2) Then I fire fetchPosts() breakpoint at ref.observeSingleEvent(of: .value, with: { (snapshot) in and I get this really long error: firebaseInstagram[4245] <Error> [Firebase/Core][I-NET901017] <Firebase/Network/ERROR> Encounter network error. Code, error: -1200, Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x60800012e1a0>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9806, NSErrorPeerCertificateChainKey=( "<cert(0x7fa0d4824600) s: *.googleapis.com i: Google Internet Authority G2>", "<cert(0x7fa0d4826000) s: Google Internet Authority G2 i: GeoTrust Global CA>", "<cert(0x7fa0d481ee00) s: GeoTrust Global CA i: Equifax Secure Certificate Authority>" ), NSUnderlyingError=0x60000005f4d0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x60800012e1a0>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9806, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9806, kCFStreamPropertySSLPeerCertificates=( "<cert(0x7fa0d4824600) s: *.googleapis.com i: Google Internet Authority G2>", "<cert(0x7fa0d4826000) s: Google Internet Authority G2 i: GeoTrust Global CA>", "<cert(0x7fa0d481ee00) s: GeoTrust Global CA i: Equifax Secure Certificate Authority>" )}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://play.googleapis.com/log, NSErrorFailingURLStringKey=https://play.googleapis.com/log, NSErrorClientCertificateStateKey=0} 2017-05-26 14:04:39.900 firebaseInstagram[4245] <Error> [Firebase/Core][I-COR000020] Error posting to Clearcut: Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x60800012e1a0>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9806, NSErrorPeerCertificateChainKey=( "<cert(..) s: *.googleapis.com i: Google Internet Authority G2>", "<cert(..) s: Google Internet Authority G2 i: GeoTrust Global CA>", "<cert(..) s: GeoTrust Global CA i: Equifax Secure Certificate Authority>" ), NSUnderlyingError=0x60000005f4d0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef:..>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9806, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9806, kCFStreamPropertySSLPeerCertificates=( "<cert(..) s: *.googleapis.com i: Google Internet Authority G2>", "<cert(..) s: Google Internet Authority G2 i: GeoTrust Global CA>", "<cert(..) s: GeoTrust Global CA i: Equifax Secure Certificate Authority>" )}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://play.googleapis.com/log, NSErrorFailingURLStringKey=https://play.googleapis.com/log, NSErrorClientCertificateStateKey=0}, with Status Code: 0 { 3) Finally when I fire through, all photos load with repeats. So I'm not sure why I'm getting the SSL error?
victor
6 years ago
I guess that setting App Transport Security to allow arbitrary uploads will NOT work in this case...
victor
6 years ago
Hey Brian, My bad. The code checks for similar photo URLs, not similar photos. Of course my posts will repeat photos if I share the same photo again because same photos will have different URLs... haha.
Casey West
6 years ago
pagination for the win :)
jameswoodward
6 years ago
Hi Brian, Loving the course so far! Just a thought; wouldnt it be better to use an extension to UIImageView instead if a creating a custom class? Saves you having to go through and rename all your imageview instances. CODE: extension UIImageView { func loadImage(url: String) { print("loading image \(url)") guard let url = URL(string: url) else { return } URLSession.shared.dataTask(with: url) { (data, response, error) in if let error = error { print("error fetching from image URL: ", error) return } //guard url.absoluteString == self.post?.imageURL else { return } guard let imageData = data else { return } DispatchQueue.main.async { self.image = UIImage(data: imageData) } }.resume() } }
Brian Voong
6 years ago
smiller193
6 years ago
Is anyone having an issue with the input accessory view getting stuck at the bottom? When exiting comment screen
Brian Voong
6 years ago
Dan Boyle
6 years ago
Everything seems to be working great with my app so far, but I noticed something interesting. My current logged in user only has 5 posts. I added another post... so there should be six... seven with the profileImageView and CustomImageView got called 14 times (because I left my print statement in there). Before I uploaded the image, CustomImageView was called about 19 to 20 times for only five images. Why would this be?
stonypig1
6 years ago
great course, but i do have one thing i am so comfused: when you check the url to avoid duplicate post: url.absolutestring != self.post?.imageUrl , why they are can be different? you said that it is in different cpu tread , and different size may load longer, but why url will change ? what i mean is even take longer to load or different queue, isn't the URL link still will be the same ? please make little more clear for me, thanks very much
stonypig1
6 years ago
can i check equality of two post's UID instead of image url ? then return/exit it if it is duplicated, thanks
Brian Voong
6 years ago
stonypig1
5 years ago
thanks so much for a prompt reply, i watched the links you provided, if i understand correctly, it is about dequed cells loads wrong images, but my app is really loading duplicate cells, text and image is the same, because i add an extra refresh controller letting user refresh the tableview/collectionview. (which i think is kinda popular exist in most social app: facebook , wechat etc.) everytime after i refreshed the tableview/collectionview, add a post, in the main timeline duplicate posts appear right away, no need to scroll at all. i do set post = [ ] back to empty. is there anyway i can use if-else condition to check that if there is duplicate Post cell uids, if there is duplicate ones ,don't let post cell add or remove it. if anyone here has ever encountered the same problem please please let me know. it is my first ever app, i try to release on app store after so long self-study, : ) , thanks
benpalmer661
5 years ago
Hi Brian, i'm looking to upload an image with imagePicker to the header of my collection view controller, i use the delgate to call the func inside the collection view, and then in the didfinishpicking i try to set the profileImage in my header to the image i just pick , only problem i use the didSet method that gets the image thats already in the header using your class customImageView and its function so what would be the best way to pick an image with image picker and have it load into the header when didfinishpicking? I gone to stack over flow but your probably the only one who knows how?
Brian Voong
5 years ago
benpalmer661
5 years ago
thanks Brian i tried putting var pickedImage: UIImage? (a shortened version of didfinish picking below) func didfinishpicking....{ pickedImage = selectedImage self.collectionView?.reloadData() dismiss(animated: true, completion: nil) } then in my header var user: User didSet - I call profileImageView.image = profileEditor.pickedImage though to no avail
Brian Voong
5 years ago
Tokyojogo
5 years ago
Hi Brian, So after sharing a post, the whole feed gets reloaded. Is there a better way of handling this (it doesn't seem optimal specially if the user has hundreds of posts already)? Is there a way to just insert a cell? Will you be addressing this?
stonypig1
5 years ago
Mr.Brian, can you tell us when to use property observer instead of a function ? it seems you used a lot in this app, but most of time i use func instead of property observer, property observer has not become second nature to me yet, please explain. thanks very much
Brian Voong
5 years ago
Sanket Ray
5 years ago
weird....my print(1) statements got executed once!
jeffery
5 years ago
Hi Brian, I wonder if this is the reason: The ReloadData calls run in separate threads, which may cause race condition?
Brian Voong
5 years ago
Abhishesh
5 years ago
If you want to order the posts in order of newest post first just add self.posts.reverse() after self.posts.append(post)
stonypig1
5 years ago
How to check post's user profile image if it loads incorrectly by the asynchronous loading, not the post main image. I think it is the similar way, does anyone knows how ? thanks
PreachOnBerto
5 years ago
Hey Brain, I'm having issues populating my collection View with the posts on a separate project. This is how my Cell is setup: class FlowerCell: UICollectionViewCell { @IBOutlet weak var flowerImageView: UIImageView! var errorMessage = "" var flower : Flower? { didSet { guard let imageURL = flower?.imageURL else { return } guard let url = URL(string: imageURL) else { return } URLSession.shared.dataTask(with: url) { (data, response, error) in if let error = error { self.errorMessage += "DataTask error: " + error.localizedDescription + "\n" } if url.absoluteString != self.flower?.imageURL { return } guard let imageData = data else { return } guard let response = response as? HTTPURLResponse, response.statusCode == 200 else { return } let photoImage = UIImage(data: imageData) performUIUpdatesOnMain { self.flowerImageView.image = photoImage } } .resume() } } } The error occurs at the "self.flowerImageView.image = photoImage" line where the error is "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value" I'm not sure why I am getting this error. The performUIUpdatesOnMain is just Dispatch call. Need some help. Thanks, Roberto
Brian Voong
5 years ago
PreachOnBerto
5 years ago
Of course. Sorry about that. I hate simple mistakes like that. This course is awesome btw!
ducward
5 years ago
Hey Brian, could you please explain how to stop the user from posting the same photo twice? From my understanding, this lesson checks for the same image url through this statement "if url.absoluteString != self.lastURLUsedToLoadImage { return ...}" but the same photo could have a different image url; therefore it could show up as duplicates in the UserProfilePhotoCell. Thanks
Brian Voong
5 years ago
ducward
5 years ago
Thank you for your quick response!
joker45
5 years ago
what you can do, is an MD5 Check of the source image and send user a warning, that the same image already posted. You have to do this how brian said, before upload/sharing it. But if the user modfining the source with any photo app, than the md5 sum changes. Or if user convert source to other size... this checks only prevent to upload "same file" not maybe same "picture" if you understand what i mean.
rickkettner
5 years ago
Here is one way to reverse the order of the posts... while adding new posts at the top as they are created. This code can be used (with perhaps minor changes) to support pagination in the future (loading in additional older posts further down the page - all in the correct reversed order). fileprivate func fetchOrderedPosts() { guard let uid = Auth.auth().currentUser?.uid else { return } let ref = Database.database().reference().child("posts").child(uid) ref.queryOrdered(byChild: "creationDate").observe(.childAdded, with: { (snapshot) in guard let dictionary = snapshot.value as? [String: Any] else { return } let post = Post(dictionary: dictionary) self.posts.insert(post, at: 0) var paths = [IndexPath]() let path = IndexPath(row: 0, section: 0) pathArray.append(path) self.collectionView?.insertItems(at: paths) }) { (err) in print("Failed to fetch ordered posts:", err) } }
rickkettner
5 years ago
Oops.. note that "pathArray.append(path)" should be "paths.append(path)" as I had renamed that.
rickkettner
5 years ago
I should note that there is almost certainly a better way to do the initial loading of reversed posts. This approach seems to be more geared towards pagination (without reloading ALL data each time). As it stands, it has an awkward load animation for the initial set of posts.
Boula
4 years ago
Hey, Brian !! I wonder about why we could just type the following: if url.absoluteString != urlString ..... instead of using another instance variable ?! i mean what is the difference between if we could make another instanceVar or just use urlString in the comparison ... Thanks
Brian Voong
4 years ago
roygbiv
4 years ago
Hi Brian, Would like to check my concept. The reason images are loaded incorrectly into the collection view cells is because the task occurs asynchronously. This means some images which have a "smaller" size are completed first while larger images take a longer time. The dequeued cells simply populate themselves based on "first come first served basis". Therefore, even though the "larger image" was the first image to be posted, due to its larger size relative to others, it got delayed and therefore bumped to the next cell. Why then is the solution this ? if url.absoluteString != self.lastURLUsedToLoadImage { return } Is it because we want to check the url string of the post to be equal to the fetched imageURL ? If they are not the same, the closure exits and restarts until the url strings match and thus populate the cells ? Is this similar to forcing the task to be executed sequentially ? i.e image1 posted first will populate cell 1; image2 posted second will populate cell 2 regardless of size.
Vinney
4 years ago
Hi, Brian I've a question that instead of creating a custom image View class why don't we create extension to the UIImageView class that has a function does the same thing.
Brian Voong
4 years ago
Vinney
4 years ago
I wrote something like this https://pastebin.com/NBsPJCQ8
Brian Voong
4 years ago
Vinney
4 years ago
Ok, thanks Brian.
Bùi Xuân Huy
4 years ago
Hi Brian. I've a question: Why when we use observe(.value), the order of post is incorrect, but when we use observe(.childAdded), the order of post is correct ? And when we reloadData (), the position of the cells is swapped, right?
Brian Voong
4 years ago
Bùi Xuân Huy
4 years ago
But when I use observe.value, the order of post is incorrect :(
Chris Davis
4 years ago
Hi Brian and all. Great stuff here! Now I'm trying to make this app using Firestore instead, using a subcollection of "posts" under each collection "user". I've gotten everything else to work, except the fetchOrderdPosts function. See below. Any suggestions? var posts = [Post]() fileprivate func fetchOrderedPosts() { guard let uid = Auth.auth().currentUser?.uid else { return } Firestore.firestore() .collection("users") .document(uid) .collection("posts") .order(by: "creationDate", descending: true) .addSnapshotListener { (snapshot, err) in if let err = err { print("Failed to get posts from user:", err) return } guard let snapshot = snapshot else { return } //*** THIS IS WHERE I'M STUCK*** //*** When i post a new photo, all hell breaks loose, and it reloads multiple copies snapshot.documents.forEach({ (data) in let dictionary = data.data() as [String : Any] let post = Post(dictionary: dictionary) self.posts.append(post) }) self.collectionView.reloadData() } }
Chris Davis
4 years ago
I know that when I add a new post, my current code just adds to the Post(dictionary), thereby creating multiple copies of the posts. I'm just missing something simple, just need to append to Post(dictionary).
Brian Voong
4 years ago
Chris Davis
4 years ago
Absolutely! Not many online tutorials like yours out there, especially ones that uses Firestore. Thank you! Any temporary suggestions for my original post?
Brian Voong
4 years ago
Chris Davis
4 years ago
I figured as much, I'm just having a hard time "massaging" the snapshot to return a dictionary. New to Firestore, so i'm a little lost.
Chris Davis
4 years ago
Found a solution, in case anyone else came up with the same problem: fileprivate func fetchOrderedPosts() { guard let uid = Auth.auth().currentUser?.uid else { return } Firestore.firestore() .collection("users") .document(uid) .collection("posts") .order(by: "creationDate", descending: true) .addSnapshotListener { (snapshot, err) in if let err = err { print("Failed to get posts from user:", err) return } self.posts.removeAll() //*** HERE'S MY FIX *** guard let snapshot = snapshot else { return } snapshot.documents.forEach({ (data) in let dictionary = data.data() as [String : Any] let post = Post(dictionary: dictionary) self.posts.append(post) }) self.collectionView.reloadData() } } If anyone else comes up with a more efficient solution, please let me know. I have no idea if this a good fix, but it works.
Kirk Washam
3 years ago
Thanks for this
jimmy hernandez
3 years ago
Hi Brian , when I dismiss MaintabbarController and go back to UserProfileController Images duplicate I used this Line for avoiding repeating images: if imageUrl.absoluteString != self.post?.imageUrl { return } but stool images repeat and when I scroll UserProfileController Images will move from cell to Another , why ?
HELP & SUPPORT