User Photo Selection
Instagram Firebase
In this lesson, we'll be implementing didSelectItem in our UICollectionView to handling the selection of our photos in the list. In order to show the selected photo, we will setup a custom header that holds reference to a UIImageView that we'll set using our selected image. In addition to this, we will analyze the behavior of efficiency and UI hanging when trying to optimize for the best user experience.

Comments (38)
johnrm9
6 years ago
The code refactoring for fetchPhotos would also work with a block property instead of a function call ... let assetsFetchOptions: PHFetchOptions = { let fetchOptions = PHFetchOptions() fetchOptions.fetchLimit = 15 let sortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false) fetchOptions.sortDescriptors = [sortDescriptor] return fetchOptions }() fileprivate func fetchPhotos(){ let allPhotos = PHAsset.fetchAssets(with: .image, options: assetsFetchOptions) ...
johnrm9
6 years ago
let targetSize = PHImageManagerMaximumSize for the header cell works as well as let targetSize = CGSize(width: 600, height: 600) override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath) as! PhotoSelectorHeader // header.photoImageView.image = self.selectedImage if let selectedImage = selectedImage, let index = self.images.index(of: selectedImage) { let selectedAsset = self.assets[index] let imageManager = PHImageManager.default() // let targetSize = CGSize(width: 600, height: 600) let targetSize = PHImageManagerMaximumSize imageManager.requestImage(for: selectedAsset, targetSize: targetSize, contentMode: .aspectFit, options: nil, resultHandler: { (image, _) in header.photoImageView.image = image }) } return header }
johnrm9
6 years ago
You might find this interesting... adding self.collectionView?.setContentOffset(CGPoint.zero, animated: true) to the following makes the whole collection view scroll to the top. override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { self.selectedImage = images[indexPath.item] self.collectionView?.reloadData() self.collectionView?.setContentOffset(CGPoint.zero, animated: true) }
saicharan123
6 years ago
Good one :) Thank you
Nick Vaughn Kang
5 years ago
For iOS11 and iPhone X: collectionView.setContentOffset(CGPoing(x: 0, y: -collectionView.safeAreaInsets.top), animated: true)
johnrm9
6 years ago
Oh, that doesn't quite work because of the scroll bar .... but this is better ... let yOffset:CGFloat = self.navigationController?.navigationBar.frame.height ?? 0 let scrollPoint = CGPoint(x: 0, y: -yOffset) self.collectionView?.setContentOffset(scrollPoint, animated: true)
johnrm9
6 years ago
Oh, I meant navigation bar not scroll bar
nikolai_georgie
6 years ago
For some reason my header cell takes up the whole page and the photocells appear scattered. I copied your code and I still end up with the same output. Any ideas how i could fix this?
Brian Voong
6 years ago
nikolai_georgie
6 years ago
Yes, I have that. I went ahead and downloaded your project. I copied all of it and compared the code side by side. When I run your project it works, when I run mine (exactly the same) it doesn't. That really puzzled me..
Brian Voong
6 years ago
Nathan Hsiao
6 years ago
Some of my photo using targetSize of width:600 , height:600, still blurry and some photo are clear. Is there way to set certain targetSize for certain photo?
Nathan Hsiao
6 years ago
if targetsize equal PHImageManagerMaximumSize it work pretty well, all the images resolution are pretty good but when fetch limit go over 10 the whole application crashes. Using PHImageManagerMaximumSize also receive a memory warning on the console.
Brian Voong
6 years ago
Nathan Hsiao
6 years ago
i am currently using iphone 6 plus.
4kidz
6 years ago
Hey Brian is there any way to implement a multiselect on the custom imagepicker??
Brian Voong
5 years ago
Dennis van Mazijk
6 years ago
Great video, Brian! This part really helped me out, because I also made my own photo collection controller a while ago. My users can upload a profile picture by selecting a photo from the collection, kind of what you did a long time ago in the messenger tutorials (creating a black background and zooming into a selected photo). The only problem I had was the annoying part of having a brick wall between the thumbnails and the original size of those pictures. Fetching the original size didn't really work, because having a huge collection of photos would take a considerable amount of time to fetch. Smaller sizes did work, but were very blurry and not suitable for the upload.
Brian Voong
5 years ago
Игорь Магурян
6 years ago
Very nice video... Just have one question, what is the logic, when you go to the original instagram app, and you open any photo, you are doing fetch photo request to the insta server, and first of all they are showing photo with very low blurred quality, and once fetching is 100% complete, they are showing original photo. Pretty similar to your logic, but I guess works absolutely different, so the question is, Do you have an idea how do they do that via internet call?
Brian Voong
5 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
Ally Makongo
6 years ago
Hi Brian, how can i remove the blur in the header?
Brian Voong
5 years ago
Rasih Caglayan
5 years ago
Hi Brian, thanks for great courses.I really like them, they are best courses i ever see about iOS; really thanks :) Is there any chance add photo directly to stream if i take a photo and then press use this photo ? I mean, i am trying to find a way skip adding photo to gallery first and select photo from there. If possible i want to take a photo and add it directly to the grid or list. Is it possible ? Can you share a documentation or link for me to achieve that goal ? Thanks.
Brian Voong
5 years ago
Rasih Caglayan
5 years ago
Wooooaaah :) What a speedy answer Brian! It is less than a minute ! Faster than billion dollar company :) Thank you very much.
Brian Voong
5 years ago
Rasih Caglayan
5 years ago
It was around 22:00 here. I remember that I sent a support request a big tech learning company about something is not work. They answer me after 1h to say "hey it works on my computer" So they have coffee too but it was different ... :) Thanks again.
Esat Kemal Ekren
5 years ago
I try to use for the optimize selected image with code in the below DispatchQueue.main.async { if let selectedImage = self.selectedImage { if let index = self.images.index(of: selectedImage) { let selectedAsset = self.assests[index] let imageManager = PHImageManager.default() let targetSize = CGSize(width: 600, height: 600) imageManager.requestImage(for: selectedAsset, targetSize: targetSize, contentMode: .default, options: nil, resultHandler: { (image, info) in header.photoImageView.image = image }) } } } also try to Dispatch.global too but not working could you give me any tips how can i move on?
Brian Voong
5 years ago
Esat Kemal Ekren
5 years ago
yes but it's not working. I'm on the wrong way i think
Esat Kemal Ekren
5 years ago
Here is the complete function : ( It's working for the fetch but not working the solve delay of image ) override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath) as! PhotoSelectorHeader header.photoImageView.image = selectedImage DispatchQueue.main.async { if let selectedImage = self.selectedImage { if let index = self.images.index(of: selectedImage) { let selectedAsset = self.assests[index] let imageManager = PHImageManager.default() let targetSize = CGSize(width: 600, height: 600) imageManager.requestImage(for: selectedAsset, targetSize: targetSize, contentMode: .default, options: nil, resultHandler: { (image, info) in header.photoImageView.image = image }) } } } return header }
Esat Kemal Ekren
5 years ago
So any advice for me?
Brian Voong
5 years ago
Esat Kemal Ekren
5 years ago
Ow i think i didn't explain my self =) Everything is working correctly on my project. But when we select the image it'll bigger and target size will be 600 x 600 so image is loading a little bit for the getting sharp. I try to improve how can we shorten that time. After i asked this question i checked instagram app time is similar with our project.
Brian Voong
5 years ago
Esat Kemal Ekren
5 years ago
I see :) I think best way the leave it current situation =) without dispatchQuene i mean =)
Esat Kemal Ekren
5 years ago
Thanks for the heads up
Alex Ak
5 years ago
If I comment out the line "options.isSynchronous = true" I'm able to get something like 500 image instantly without having to put the call onto a separate thread (although the quality is fairly low despite having specified that I want 600x600 as target size).
Laurent Maquet
5 years ago
Any idea why my header image is "shivering" while viewForSupplementaryElementOfKind is dequeueing reusable supplementary view ? Oddly, this shivering effect will happen only at first reloadData(), but and not after selecting a new image from the grid.
Laurent Maquet
5 years ago
Found out that I forgot a condition for reloading collectionView after fetching photos : if count == allPhotos.count - 1 { DispatchQueue.main.async { self.collectionView?.reloadData() } }
charan
5 years ago
Wow. Lot of content. Learning at a super pace. Super Job!
hyunah
5 years ago
You are best teacher yeah
Jafar
5 years ago
I've learned more in 5 lessons of the Instagram Firebase course than 50+ lessons on Udemy. This course is truly a shortcut to becoming a pro at iOS development. Brian, your pacing and teaching methods are perfect! Future app tutorial idea: A retailer locator, or an app that implements both MapKit for pinpointing and Alamofire for networking. I think having a MapKit series would fit nicely into your collection of iOS tutorials.
stonypig1
5 years ago
does anyone know how to collect multi photos a one time, and post them togather ? thanks
Santa
5 years ago
Looking for the same! let me know if you find anything out there=)
AndreaMich
5 years ago
Maybe this can help who as me have friends that have a lot of photos on iCloud. If you try to select a photo from the selector, with old phone as the 5s the app return a blank header. So I found a solution that is not perfect but it works. Into the requestImage of the header I wrote some options that allow to download the photo and after put it into the header: let options = PHImageRequestOptions() options.isSynchronous = true options.isNetworkAccessAllowed = true This however, during the download, make the view unresponsive, but only for a second and only with the photo on the cloud. But unfortunately this is the only solution that I have for the moment.
AndreaMich
5 years ago
var header: PhotoSelectorHeader? override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath) as! PhotoSelectorHeader self.header = header header.photoImageView.image = selectedImage if let selectedImage = self.selectedImage { if let index = self.images.index(of: selectedImage) { let selectedAsset = self.assets[index] let imageManager = PHImageManager.default() let options = PHImageRequestOptions() options.isSynchronous = true options.isNetworkAccessAllowed = true let targetSize = CGSize(width: 500, height: 500) imageManager.requestImage(for: selectedAsset, targetSize: targetSize, contentMode: .default, options: options, resultHandler: { (image, info) in header.photoImageView.image = image }) } } return header }
atran
5 years ago
The reason it hangs a bit is because you're executing it from the main thread. You can try putting the code in the background just like the way you're fetching photos for the grid. And as for the blank header, I think it's because the code to fetch the image for the header is asynchronous. So the code will first "return header" which has no image in it. So, you can try setting header.photoImageView.image = image, just before the return, so that it will return some low-resolution image first and then when fetching the higher resolution image is done, you can update the header.
rehan1531
5 years ago
wow wow wow wow wow wow :-) I don't know how to say you Thank you :-) One thing I am struggling through iOS development is that I forget most of the things , is there any hack/ method or way to handle such issue with myself ?
Brian Voong
5 years ago
rehan1531
5 years ago
Thank you Brian :-) :-) I Love the way you response to Question :-) :-)
Aivars
5 years ago
Hi, what is the best way do implement image cropping here, similar as used in the Instagram? You are able to zoom and move around the image in the header cell. Didn't find that in the description of any of following videos. Should it be scroll view in the header cell with image view on the top of it?
Brian Voong
5 years ago
Adrian Ludvigsson
5 years ago
Why wouldn't you subclass from photoSelectorCell to PhotoSelectorHeader instead of copying the code?
Brian Voong
5 years ago
rickkettner
5 years ago
One nice trick is to combine the two "if let" statements as a single line: if let selectedImage = selectedImage, let index = self.images.index(of: selectedImage) { // } More concise code and still quite readable.
Christophe Bugnon
5 years ago
Hey Brian, I had to use reloadInputViews() instead of reloadData() because with reloadData() it create me a double of all my images in my array with the method didSelectItemAt. Did you know why ? Best regards.
Christophe Bugnon
5 years ago
It's ok I've found my error, just forgot to put my "options" in requestImage method... - -' Have a nice day ! ;)
thenny chhorn
4 years ago
Wow , I'd had the same problem. And thanks to your following comment. I now solved the problem of fetching images twice of the option limit. thank you
Juan Cabral
4 years ago
Had the same issue and resolved after your comment, thanks!
Christophe Bugnon
5 years ago
I've found something, if you use .aspectFill instead of .aspectFit your header image will not be blurring whatever size of picture you use.
Brian Voong
5 years ago
Christophe Bugnon
5 years ago
That's true ! :)
Alex Du
5 years ago
any one have a problem with the index out of range in the viewforsupplementaryElementofKind?
Alex Du
5 years ago
Yeah, I fixed it. My bad. Really stupid mistake. But it is a good way to learn
Boula
5 years ago
is it true to set the following line: let targetSize = CGSize(width: view.frame.width, height: view.frame.width) as why we have to set it by 600x600, Brian ?!
darren100
4 years ago
these videos are nice. im thinking about ubsubbing to raylinderwich cuz they are so fking expensive.
f.ramzdev13
4 years ago
How can you just refresh the headerView? Want I wanted was to add a UIView background color black when selected & a red color when selected. But when I use collectionView.reloadData() it acts kind of funny. So is there a way to just reload the header?
이동건
4 years ago
Hello brain! Thanks to your great tutorial but i have one thing that I couldn't solve On my real device iPhone X, there are too many photos to fetch at once So empty header, collectionviewcells are waiting for fetching all photos too long how I can I solve this problem? firstly, I tried to reload data like this if count % 100 == 0 && count != 0 { self.collectionView.reloadData() } but it reloads collectionView and Header View too many, So while fetching all photos I couldn't pick new photo. because it still fetching, reloading and show first asset for headerView is there any good idea or solution for large amount of photos ( like real device) ?
이동건
4 years ago
I solved my problems. I didn't fetch all photos in fetchPhotos at once First, I retrieve PHAssetResult<PHAsset) from PHAsset.fetchAssets(with: .image, options: options) and reload UICollectionView Next, I fetch photo from PHAssetResult<PHAsset) at cellForItemAt, because we can access each element of PHAssetResult<PHAsset) by index (subscript is available) So there is no delay when fetching a lot of photos in real device Please comment below the your opinion about my way
Kenny Ho
4 years ago
Hi everyone, it seems that since Swift 4.2 there's a bug regarding selecting a photoCell. The headerCell image does a weird glitch update and instantly disappears. I downloaded that final project and the bug was there as well. Anyone solved this bug?
Brian Voong
4 years ago
waleed
4 years ago
Hello Everyone, I seem to be getting an error with this part of the lesson it start with thread 33 signal SIGABRT _abort_with_payload and when I try to debug I get Message from debugger: The LLDB RPC server has crashed. The crash log is located in ~/Library/Logs/DiagnosticReports and has a prefix 'lldb-rpc-server'. Please file a bug and attach the most recent crash log. when I try some more by refreshing and cleaning the simulator and try to skip the error I get Message from debugger: Terminated due to signal 9 Any help is appreciated? I think the start is the main message that keeps coming up
waleed
4 years ago
Solved After spending several days trying to solve the problem I decided just to re write the whole code again and it worked I must have done something wrong when I was writing the code
malrhex
4 years ago
How can you be so confident when you don't need a self.
Brian Voong
4 years ago
Clint Larenz Nurse
4 years ago
Probably not the correct place to ask this, I'm skimming through these lessons to see what is ahead. I noticed there is no lesson on how to send direct messages. Do you have a course I can purchase regarding that Brain?
yaostyle
4 years ago
I've added additional check to see if the cureent selectedImage is same as the image we're tapping, then skip the whole process of getting new image again by PHimageManager: if let index = self.images.firstIndex(of: selectedImage) { let selectedAsset = self.assets[index] let imageManager = PHImageManager.default() let targetSize = CGSize(width: 600, height: 600) imageManager.requestImage(for: selectedAsset, targetSize: targetSize, contentMode: .aspectFit, options: nil) { (image, info) in header.photoImageView.image = image } }
MehmetEmre
3 years ago
Hey everyone a quick question. What if we want to show also videos like instagram how we can do this ?
MehmetEmre
3 years ago
Any idea ?
HELP & SUPPORT