UICollectionView with array of images set it right - ios

I have a question. Inside UICollectionView I have UIImage with Content Mode - Aspect Fit... Now I need that in UICollectionView images show one near another. Now I have a lot of space between them.
extension UserDetailInformationOfAnimalViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
//MARK: - Collections
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let post = postsArray[collectionView.tag]
// print("count: ", post.imagesLink.count)
return post.imageLinks.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
let post = postsArray[collectionView.tag]
// print("postArrayPhotos: ", post.imagesLink.count)
if post.imageLinks.count != 0 {
// print("POST = ", post)
// print("Look here: ", post.imagesLink[indexPath.row].imageLink)
// print("IMAGELINK = ", post.imagesLink[indexPath.row].imageLink)
let imageLink = post.imageLinks[indexPath.row]
if imageLink.imageLink != nil {
let url = URL(string: imageLink.imageLink!)
print("IMAGELINK = ", imageLink)
cell.imageOfAnimalInCollectionView.sd_setImage(with: url!, placeholderImage: UIImage(named: "App-Default"),options: SDWebImageOptions(rawValue: 0), completed: { (image, error, cacheType, imageURL) in
})
}
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height = self.view.frame.size.height;
let width = self.view.frame.size.width;
// in case you you want the cell to be 40% of your controllers view
return CGSize(width: width, height: height * 0.35)
}
}

Use this method to set width of cell dynamically
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if indexPath.row == whatever you want {
return CGSize(width: collectionView.frame.size.width/2, height: collectionView.frame.size.width/2)
}
}

I understand your problem. Here I give you a way around. This solution may not effective for you but it will give you idea how to solve it. Main concept is the you have to return cell size according to image. But instantly image is not in your hand. Its on the server. So you can do that fist return a demo or place holder image height then after finishing download actual image from server tell the collection view to re-layout the cell according to actual image size.
cell.imageOfAnimalInCollectionView.sd_setImage(with: url!, placeholderImage: UIImage(named: "App-Default"),options: SDWebImageOptions(rawValue: 0), completed: { (image, error, cacheType, imageURL) in
//told the collection view to relayout according to image size
collectionView.collectionViewLayout.invalidateLayout()
})
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cell = collectionView.cellForItem(at: indexPath) as! CollectionViewCell
return cell.imageOfAnimalInCollectionView.image.size! // Put your logic here, until actual image download from server you can return a default size.
}

Related

Swift: UICollectionView sizeForItemAt will never be executed

I'm making a CollectionView which has 2 sections, everything is fine but the size. I'd like to give different size of cell in different section, but when I call sizeForItemAt, Xcode shows a yellow warning "Will never be executed". Here's how I set my CollectionView functions:
extension ArtistProfileViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch mySections[section] {
case .profile:
return 1
case .relatedArtists:
return 10
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch mySections[indexPath.section] {
case .profile:
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ArtistProfileCell.identifier, for: indexPath) as? ArtistProfileCell else { return UICollectionViewCell()}
return cell
case .relatedArtists:
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RelatedArtistCell.identifier, for: indexPath) as? RelatedArtistCell else { return UICollectionViewCell()}
cell.myImageView.image = UIImage(systemName: "person")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: 100)
}
}
And here is how I set my CollectionView:
var myCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.estimatedItemSize = .zero
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.register(ArtistProfileCell.self, forCellWithReuseIdentifier: ArtistProfileCell.identifier)
cv.register(RelatedArtistCell.self, forCellWithReuseIdentifier: RelatedArtistCell.identifier)
return cv
}()
I did set the CollectionView.delegate/datasource to self in viewDidLoad, and I can see the cell appear on the screen. However, the size of cell does not change at all. Plus, I know one of the solution is to set the estimated size to none in storyboard, but I don't know how to set it programmatically. So I don't know if it works.
There's one thing odd, when I call sizeForItemAt, it does not show the complete function automatically. I have to type it by myself like this.
Does anyone can help? Thanks in advance!
You put sizeForItemAt inside cellForItemAt. They need to be separate.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch mySections[indexPath.section] {
case .profile:
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ArtistProfileCell.identifier, for: indexPath) as? ArtistProfileCell else { return UICollectionViewCell()}
return cell
case .relatedArtists:
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RelatedArtistCell.identifier, for: indexPath) as? RelatedArtistCell else { return UICollectionViewCell()}
cell.myImageView.image = UIImage(systemName: "person")
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: 100)
}
Try selecting your code, then pressing Ctrl + i to re-format your code (fixes indenting too!)

Swift: ScrollView Scrolling as per Cell Image Size

I have a viewController which has CollectionView and the collectionView cell contain the ImageView and response is from the api. And the viewController orientation is Landscape. So issue is when i scroll the image it's not scrolling properly. Here is the attach image.
So how can I set the Scrolling action as per the my image Size. So it's display only image in View. Here is my code.
extension GalleryViewController : UICollectionViewDataSource , UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageCell
let image = arrImgs[indexPath.row]
cell.lblIndex.text = "< \(indexPath.row+1) / \(arrImgs.count) > Images"
if let imgUrl = URL.init(string: image.Message) {
cell.imgV.sd_setImage(with: imgUrl) { (img, error, casheType, url) in
DispatchQueue.main.async {
cell.imgV.image = img
}
}
}
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arrImgs.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize.init(width: width, height: height)
}
}
class ImageCell: UICollectionViewCell {
#IBOutlet var lblIndex: UILabel!
#IBOutlet var imgV: UIImageView!
}
This is my StoryBord Arrangement.
Thanks.

Call collectionView(_ :layout:sizeForItemAt:) in Swift 4

I made the gallery using UICollectionView.
If you lower the screen, the image should continue to come out and stop at 20.
The code was constantly updated.
However, the function collectionView (_: layout: sizeForItemAt :) that sets the size of the cell did not work.
Therefore, only 20 images are displayed continuously.
On viewDidLoad()
if let layoutt = artCollectionView?.collectionViewLayout as? PinterestLayout
{
layoutt.delegate = self
}
cell setting
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HomeCollectionViewCell", for: indexPath) as? HomeCollectionViewCell,
let arts = self.artList else { return HomeCollectionViewCell() }
if arts.count > indexPath.row
{
let model = arts[indexPath.row]
cell.imgView.sd_setImage(with: URLHelper.createEncodedURL(url: model.thumburl), completed: nil)
}
return cell
}
return cell size
extension HomeViewController : PinterestLayoutDelegate
{
func collectionView(_ collectionView: UICollectionView, heightForPhotoAtIndexPath indexPath:IndexPath) -> CGFloat
{
return CGFloat(300.0)
}
}
Currently, the function only executes once when the app starts. Do you know how to run that function every time I want?
You need to implement UICollectionViewDelegateFlowLayout.
You can read more about the protocol here:
UICollectionViewDelegateFlowLayout protocol
Example:
extension viewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 50, height: 300)
}
}
If you look at the prepare() method of PinterestLayout class, you can see the below code
guard
cache.isEmpty == true,
let collectionView = collectionView
else {
return
}
When you reload the colectionView the cache is no longer empty, so it returns back without altering the layout.
So just remove the cache value when prepare is called.
Add removeAll() before guard
cache.removeAll()
Please follow the steps to install PinterestLayout in UICollectionView below.
Set PinterestLayout to your collection view.
let layout = PinterestLayout()
collectionView.collectionViewLayout = layout
Setup layout
layout.delegate = self
layout.cellPadding = 5
layout.numberOfColumns = 2
Implement methods of PinterestLayoutDelegate
extension CustomCollectionVC: PinterestLayoutDelegate {
func collectionView(collectionView: UICollectionView,
heightForImageAtIndexPath indexPath: IndexPath,
withWidth: CGFloat) -> CGFloat {
let image = images[indexPath.item]
return image.height(forWidth: withWidth)
}
func collectionView(collectionView: UICollectionView,
heightForAnnotationAtIndexPath indexPath: IndexPath,
withWidth: CGFloat) -> CGFloat {
let textFont = UIFont(name: "Arial-ItalicMT", size: 11)!
return "Some text".heightForWidth(width: withWidth, font: textFont)
}
}
When data is loaded invalidate layout as well as reload data on collection view.
collectionView.collectionViewLayout.invalidateLayout()
collectionView.reloadData()
Observation:
func collectionView(_ collectionView: UICollectionView, heightForPhotoAtIndexPath indexPath:IndexPath) -> CGFloat
try this instead of above:
func collectionView(collectionView: UICollectionView,
heightForImageAtIndexPath indexPath: IndexPath,
withWidth: CGFloat) -> CGFloat

How to show rect of specific cell in UICollectionView

I am using UICollectionView in a viewcontroller and passed an array of images. I have enabled paging of collection view . But when i scroll to next image , the previous cell does not disappear completely. It remains at the edge. So, how I can make only specific image/cell visible while scrolling left/Right.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == collection_LargeView
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Large Cell", for: indexPath)as! LargeImageCell
cell.imageView_LargePic.sd_setImage(with: NSURL(string: array[indexPath.row]) as URL! )
cellIndex = indexPath
return cell
}
else
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Picture Cell", for: indexPath)as! VendorPictureCell
cell.imageView_VendorPics.sd_setImage(with: NSURL(string: array[indexPath.row])as URL!)
return cell
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return array.count
}
//when item at bottom collection view is selected the other collection view scroll to that indexPath
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == collectionView_picturesCollection
{
Index = indexPath.row
collection_LargeView.scrollToItem(at: indexPath, at: .right, animated: true)
}
}
Please solve this.
Thank You.
After scrolling from one cell to next Screenshot : https://i.stack.imgur.com/msmnV.jpg
You fist change in your story board's collectionview's in min spacing for cell and for lines to 0 like in below images:
To
I hope it may help you
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if collectionView == collection_LargeView
{
return CGSize(width: self.view.frame.size.width, height: 524.0)
}
else{
return CGSize(width: 86.0, height: 74.0)
}
}

How to implement UICollectionViewDelegateFlowlayout's sizeForItemAtIndexPath with Async images?

I'm trying to implement UICollectionViewDelegateFlowlayout's sizeForItemAtIndexPath similar to this tutorial: http://www.raywenderlich.com/78550/beginning-ios-collection-views-swift-part-1
The problem I have is that my photos are downloaded via an async call on the web, so when sizeForItemAtItemPath is called, my images haven't loaded yet. In other words, the code below crashes on imageArray. How can I delay the method until my images are loaded? I've also tried using a hardcoded with and height value if no images are available, but then I only return those hardcoded values, i.e. sizeForItemAtIdexPath doesn't seem to get called again after the images are loaded.
func pinImageForIndexPath(indexPath: NSIndexPath) -> UIImage {
return imageArray[indexPath.row]
}
}
extension PinCollectionViewController : UICollectionViewDelegateFlowLayout {
//1
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let pinImage = pinImageForIndexPath(indexPath)
//2
if var size = pinImage.size as? CGSize {
size.width += 10
size.height += 10
return size
}
return CGSize(width: 100, height: 100)
}
//3
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAtIndex section: Int) -> UIEdgeInsets {
return sectionInsets
}
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! PinCollectionViewCell
cell.pinImage?.pin_updateWithProgress = true
cell.pinImage?.image = nil
var actInd: UIActivityIndicatorView = UIActivityIndicatorView()
actInd.frame = cell.pinImage.bounds
actInd.hidesWhenStopped = true
actInd.activityIndicatorViewStyle =
UIActivityIndicatorViewStyle.Gray
cell.addSubview(actInd)
actInd.startAnimating()
if let pinImageURL = self.pins[indexPath.row].largestImage().url {
cell.pinImage?.pin_setImageFromURL(pinImageURL, completion: ({ (result : PINRemoteImageManagerResult) -> Void in
actInd.stopAnimating()
if let image = result.image {
self.imageArray.append(image)
}
self.collectionView?.reloadItemsAtIndexPaths([indexPath])
}))
}
return cell
}
Indeed, you should return some hardcoded values until you haven't loaded image. Strictly saying, you should display some predefined placeholder for not yet loaded image.
When your image get loaded (when async download operation successfully completes), you should call -reloadItemsAtIndexPaths: on collection view with index path's of those cells, images for which are available.

Resources