Two cells in single CollectionView of different dimensions - ios

I have two cells within a single collection view. They both have different dimensions but for some reason on runtime both of the cells appear to have the same dimension.
Here's my setup:
internal func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if cellType == .books {
return books.count
} else if cellType == .spotlights {
return spotlights.count
} else {
return 0
}
}
internal func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if cellType == .books {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "booksCollectionCell", for: indexPath) as! BooksCollectionCell
let bookTitle = books[indexPath.item].title
let authors = books[indexPath.item].authors
cell.configureCell(title: bookTitle, authorNames: authors)
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "spotlightsCollectionCell", for: indexPath) as! spotlightsCollectionViewCell
cell.configureCell(image: #imageLiteral(resourceName: "testImage"))
return cell
}
}
Here's my storyboard screenshots:
and this is setup for collectionView:
edit: So I managed to get somwhere (thanks to cpatmulloy) by putting this code in cellForItem:
cell.frame = CGRect(x: cell.frame.origin.x, y: cell.frame.origin.y, width: 180.0, height: 130.0)
However, here's the result (look at the last cell in tableview):

Try explicitly setting the cell's height and width in the cellForItemAtIndex Path function.
Be sure to set these example properties (i.e bookCellWidth) yourself
internal func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if cellType == .books {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "booksCollectionCell", for: indexPath) as! BooksCollectionCell
let bookTitle = books[indexPath.item].title
let authors = books[indexPath.item].authors
cell.configureCell(title: bookTitle, authorNames: authors)
cell.frame = CGRect(x: cell.frame.origin.x, y: cell.frame.origin.y, width: bookCellWidth, height: bookCellHeight)
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "spotlightsCollectionCell", for: indexPath) as! spotlightsCollectionViewCell
cell.configureCell(image: #imageLiteral(resourceName: "testImage"))
cell.frame = CGRect(x: cell.frame.origin.x, y: cell.frame.origin.y, width: spotlightCellWidth, height: spotlightCellHeight)
return cell
}
}

Ok so I managed to fix it!
First I added UICollectionViewDelegateFlowLayout to the cell class, then:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if cellType == .spotlights {
return CGSize(width: 250.0, height: 180.0)
} else {
return (collectionViewLayout as! UICollectionViewFlowLayout).itemSize
}
}

Related

Set image height from image URL to PinterestLayout CollectionView

I create a CollectionView with PinterestLayout, so this is a collection view with different cells hight. It's work, but when I put my code to the project with Firestore (when image in cells I take from URL) I have a problem, because in this line "let photo = post.floverImg" I think I have to put something like in cell "cell.imageView.sd_setImage(with: URL(string: flovers[indexPath.row].floverImg!))" to show Image and not String.
extension HomeViewController {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return flovers.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.imageView.sd_setImage(with: URL(string: flovers[indexPath.row].floverImg!))
cell.label.text = flovers[indexPath.row].floverName
// cell.descriptionLabel.text = flovers[indexPath.row].floverName
cell.layer.cornerRadius = 15
return cell
}
}
func collectionView(collectionView: UICollectionView, heightForPhotoAt indexPath: IndexPath, with width: CGFloat) -> CGFloat
{
if let post = posts?[indexPath.item], let photo = post.floverImg {
let boundingRect = CGRect(x: 0, y: 0, width: width, height: CGFloat(MAXFLOAT))
let rect = AVMakeRect(aspectRatio: photo.size(), insideRect: boundingRect)
return rect.size.height
}
return 200
}
https://ibb.co/J3KdDtG
https://ibb.co/JzXZv3V

How to match the horizontal collectionView Cell's width on it's content size

I know some related questions already there regarding this but I tried those before and still no luck.
Here is my problem on the following screenshot
My current screen shows a fixed cell size and it displays empty spaces for smaller contents and larger contents going over the cell with dots..
I wanted like below
it should match the width of the product category name content.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.catCollectionView{
let catCell = collectionView.dequeueReusableCell(withReuseIdentifier: "catCell", for: indexPath)
as! catCell
DispatchQueue.main.async {
catCell.configureCell()
let catCountInt = self.catCountArray[indexPath.row]
catCell.catCountLabel.text = String(catCountInt)
catCell.catNameLabel.text = self.categoryNameArray[indexPath.row]
catCell.catCountLabel.sizeToFit()
catCell.catNameLabel.sizeToFit()
}
return catCell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let catCell = collectionView.dequeueReusableCell(withReuseIdentifier: "catCell", for: indexPath)
as! catCell
catCell.catNameLabel.text = self.categoryNameArray[indexPath.item]
catCell.catNameLabel.sizeToFit()
let labelWidth = catCell.catNameLabel.frame.width + 10
print("frame width: \(labelWidth)")
return CGSize(width: labelWidth, height: 21)
}
}
Maybe I'm missing a simple thing here but I couldn't quite figure out at this moment. Please help me and sorry for my strange English.
Let's imagine you are using a simple UICollectionViewCell subclass with constraints set up correctly in storyboard (label is pinned to all four sides of its superview) like this:
class CategoryCell: UICollectionViewCell {
#IBOutlet var nameLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
layer.borderWidth = 1
}
}
Then you can simply let auto layout determine the cells' sizes:
class CollectionViewController: UICollectionViewController {
let categories = [
"All Products",
"Fresh",
"Health & Beauty",
"Beverages",
"Home & life"
]
private var flowLayout: UICollectionViewFlowLayout? {
return collectionViewLayout as? UICollectionViewFlowLayout
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.backgroundColor = .white
flowLayout?.sectionInset = .init(top: 15, left: 15, bottom: 15, right: 15)
flowLayout?.sectionInsetReference = .fromSafeArea
flowLayout?.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
DispatchQueue.main.async {
self.flowLayout?.invalidateLayout()
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return categories.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CategoryCell", for: indexPath) as! CategoryCell
cell.nameLabel.text = categories[indexPath.row]
return cell
}
}
Result:
Instead of dequeuing another cell in collectionView(_:layout:sizeForItemAt:), you can simply calculate the width of categoryName using size(withAttributes:) on the categoryName, i.e.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let text = self.categoryNameArray[indexPath.row] {
let cellWidth = text.size(withAttributes:[.font: UIFont.systemFont(ofSize:14.0)]).width + 10.0
return CGSize(width: cellWidth, height: 21.0)
}
in attributes, give whatever font you want the categoryName to be.

Make CollectionView Cell horizontal cell bigger than others

I have a collectionView and want to make the center cell is bigger than other cells and when move to previous or next cell make it bigger in center
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.collectionView {
return slidData.count
} else {
return categoryData.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.collectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath) as! SliderImgCell
cell.categoryName.text = slidData[indexPath.row].imageTitle
cell.detail.text = slidData[indexPath.row].imageContent
cell.pics = slidData[indexPath.item]
return cell
} else{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "categoryCell", for: indexPath) as! CategoryCell
cell.categoryName.text = categoryData[indexPath.row].depName
cell.pics = categoryData[indexPath.item]
return cell
}
}
You need to save the selected indexPath like this:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedIndexPath = indexPath
}
Your collection view must confirm UICollectionViewDelegateFlowLayout. Then in sizeForItemAt method resize your cell:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath == selectedIndexPath {
return CGSize(width: 200, height: 90)
} else {
return CGSize(width: 180, height: 80)
}
}
Basically you want a Carousel animation.
Take a look into this library and either use full of it or take help from the code.
You can achieve this by using ScrollViewDidScroll(_:) method and find the center cell and enlarge it.
I wrote clear comments on the code below, if you want to go into details check-out my article about this:
https://medium.com/#sh.soheytizadeh/zoom-uicollectionview-centered-cell-swift-5-e63cad9bcd49
func setFocusedCellStyle(_ scrollView: UIScrollView) {
guard scrollView is UICollectionView else { return }
// get the centerPoint
let centerPoint = CGPoint(x: self.collectionView.frame.size.width / 2 + scrollView.contentOffset.x, y: self.collectionView.frame.size.height / 2 + scrollView.contentOffset.y)
// get the indexPath for the cell at center
if let indexPath = self.collectionView.indexPathForItem(at: centerPoint), self.centerCell == nil {
// centerCell is instance variable of type: YourCell
self.centerCell = (self.colorPickerCollectionView.cellForItem(at: indexPath) as! CustomCollectionViewCell)
UIView.animate(withDuration: 0.2) {
// Choose desired scale value
self.transform = CGAffineTransform(scaleX: 1.58, y: 1.58)
}
}
if let cell = self.centerCell { // center cell is global variable
let offsetX = centerPoint.x - cell.center.x // get to current position of the cell
// when cell is 15 pixels away from the center to the sides
// reset the cell size.
if offsetX < -15 || offsetX > 15 {
UIView.animate(withDuration: 0.2) {
self.transform = CGAffineTransform.identity
}
self.centerCell = nil // set this to nil so next cell in the center will enter the above scope
}
}
}

Scroll conflict for multiple CollectionView in a ViewController

I have two UICollectionView collectionViewA and collectionViewB in my ViewController, collectionViewB is presented as a subView when a button is tapped, the issue i have now is that when i scroll on collectionViewB, collectionViewA scrolls too, is there a way to only scroll for the active collectionView without affecting the second.?
extension TrendListVC: UICollectionViewDelegate,
UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.collectionViewForSubView{
return count
}
return 60
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.collectionViewForSubView{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ModalViewCell
//let setting = settings[indexPath.item]
//cell.setting = setting
return cell
}else{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell2", for: indexPath) as! CollectionCell
cell.itemNameLabel.text = "Name".uppercased()
return cell
}
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
var reusableView : CollectionHeader? = nil
if collectionView == self.collectionView{
if (kind == UICollectionElementKindSectionHeader) {
let head = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "headerCell", for: indexPath) as! CollectionHeader
head.trendListVc = self
head.headerHeightConstraint = headerHeightConstraint
reusableView = head
}
}
return reusableView!
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if collectionView == self.collectionViewForSubView{
if count % 2 != 0 && indexPath.item == count - 1{
let paddingSpace = sectionInsets.left * (itemsPerRowForSubView + 1)
let availableWidth = collectionViewForSubView.frame.width - paddingSpace
return CGSize(width: availableWidth + 23, height: cellHeight)
}else{
let paddingSpace = sectionInsets.left * (itemsPerRowForSubView + 1)
let availableWidth = collectionViewForSubView.frame.width - paddingSpace
let widthPerItem = availableWidth / itemsPerRowForSubView
return CGSize(width: widthPerItem, height: cellHeight)
}
}else{
let paddingSpace = sectionInsets.left * (2 + 1)
let availableWidth = view.frame.width - paddingSpace
let widthPerItem = availableWidth / itemsPerRow
return CGSize(width: widthPerItem + 4, height: widthPerItem)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if collectionView == self.collectionView{
return CGSize(width: view.frame.width, height: 50)
}else{
return CGSize(width: 0, height: 0)
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == self.collectionView{
showControllerForSetting(setting: "Name")
}else if collectionView == self.collectionViewForSubView{
print("Some thing")
}
}
}
This is most likely due to using your ViewController as delegate and datasource for both of your collection views. You need to create separate delegate classes for each collection view.

How to add an extra static cell to my UICollectionView?

I have an array of photos that I currently display in a UICollectionView. The only thing I still want to add is an extra static cell that should give the user the possibility to open the camera. I used an if-else statement to detect the index. Unfortunately, the console gives me an out of index error.
To be precise: I want this static cell to be in the top left corner, followed by my array of images. Do I have to add two sections, or should I register another custom cell to accomplish this? As of now I can see my extra cell, but it's not working when tapped (out of index).
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageArray.count + 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: photoId, for: indexPath) as! PhotosCollectionViewCell
if indexPath.row == imageArray.count {
cell.backgroundColor = UIColor.lightGray
cell.addGestureRecognizer(UIGestureRecognizer(target: self, action: #selector(tappedCamera)))
} else {
cell.imageView.image = imageArray[indexPath.item]
cell.imageView.addGestureRecognizer(UIGestureRecognizer(target: self, action: #selector(tappedPhoto)))
}
return cell
}
Updated code (solution)
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageArray.count + 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row == 0 {
let cameraCell = collectionView.dequeueReusableCell(withReuseIdentifier: cameraId, for: indexPath) as! CameraCollectionViewCell
return cameraCell
}
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tappedPhoto))
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: photoId, for: indexPath) as! PhotoCollectionViewCell
cell.imageView.image = imageArray[indexPath.row - 1]
cell.imageView.addGestureRecognizer(tapGesture)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.row == 0 {
print("Camera")
}
}
var startingFrame: CGRect?
var blackBackGroundView: UIView?
var selectedImageFromPicker: UIImage?
var selectedImageCompressed: UIImage?
func tappedPhoto(sender: UIGestureRecognizer) {
if let indexPath = self.collectionView?.indexPathForItem(at: sender.location(in: self.collectionView)) {
let imageView = self.collectionView?.cellForItem(at: indexPath)
startingFrame = imageView?.superview?.convert((imageView?.frame)!, to: nil)
let zoomingImageView = UIImageView(frame: startingFrame!)
zoomingImageView.image = imageArray[indexPath.row - 1]
zoomingImageView.isUserInteractionEnabled = true
zoomingImageView.contentMode = .scaleAspectFill
zoomingImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleZoomOut)))
if let keyWindow = UIApplication.shared.keyWindow {
blackBackGroundView = UIView(frame: keyWindow.frame)
blackBackGroundView?.backgroundColor = UIColor.black
blackBackGroundView?.alpha = 0
keyWindow.addSubview(blackBackGroundView!)
keyWindow.addSubview(chooseLabel)
keyWindow.addSubview(zoomingImageView)
// Set selected image and compress
selectedImageFromPicker = imageArray[indexPath.row - 1]
selectedImageCompressed = selectedImageFromPicker?.resized(withPercentage: 0.1)
chooseLabel.rightAnchor.constraint(equalTo: keyWindow.rightAnchor, constant: -25).isActive = true
chooseLabel.bottomAnchor.constraint(equalTo: keyWindow.bottomAnchor, constant: -25).isActive = true
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackBackGroundView?.alpha = 1
self.chooseLabel.alpha = 1
let height = self.startingFrame!.height / self.startingFrame!.width * keyWindow.frame.width
zoomingImageView.frame = CGRect(x: 0, y: 0, width: keyWindow.frame.width, height: height)
zoomingImageView.center = keyWindow.center
}, completion: {(completed) in
// Do nothing
})
}
}
}
Do I have to add two sections, or should I register another custom
cell to accomplish this?
In your case, just adding one cell at the beginning of the collection should be fair enough, there is no need to multi-section it.
Your methods should be implemented as follows:
1- numberOfItemsInSection method: should be as is:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageArray.count + 1
}
2- cellForItemAt method: depends on the first cell, if it should be a different cell:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// first row
if indexPath.row == 0 {
let cameraCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cameraCell-ID", for: indexPath)
// setup the cell...
return cameraCell
}
let defaultCell = collectionView.dequeueReusableCell(withReuseIdentifier: "defaultCell-ID", for: indexPath)
// setup default cell...
return defaultCell
}
Or, if you want it to be the same cell, but with some editions:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell-ID", for: indexPath)
// first row
if indexPath.row == 0 {
// setup the cell as cemera cell...
} else {
// setup the cell as default cell...
}
return cell
}
Actually, there is no need to add UITapGestureRecognizer for each cell, all you have to do is to implement collection​View(_:​did​Select​Item​At:​) delegate method:
Tells the delegate that the item at the specified index path was
selected.
3- didSelectItemAt method:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.row == 0 { // camera cell
// handle tapping the camera cell
} else { // default cells
// handle tapping the default cell
// don't forget that:
// getting the first element in 'imageArray' should be imageArray[indexPath.row - 1]
}
}
Hope this helped.

Resources