Dynamic UICollectionView in UIViewController - ios

I am an android developer and developing my first iOS app, I need to implement GridView in order to add some social icon so after some search I found UICollectionView but it's not working as expected. How to set dynamic height of the UICollection view? Actually I want to display all icons but it showing only a row and others after scrolling the UICollectionView.
below is the example:
This is what I am getting
This is what I want:
I am using below code:
import UIKit
class CollectionViewDemo: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var socialHandleCollection: UICollectionView!
#IBOutlet weak var socialImageView: UIImageView!
var socialHandleArray:[String] = ["Facebook", "Twitter", "Youtube","Vimeo", "Instagram", "Custom URL", "Linkedin", "pinterest"]
override func viewDidLoad() {
self.socialHandleCollection.delegate = self
self.socialHandleCollection.dataSource = self
// socialHandleCollection.frame.size.height = 130
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return socialHandleArray.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: colvwCell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! colvwCell
cell.imgCell.image = UIImage(named: "demo_img.png")
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print(self.socialHandleArray[indexPath.row])
}
}
Any help would be appreciated.

Assuming you are using autoLayout
Initially declare a specific number of columns that you desire
let numberOfColumns = 5
Okay so first things first. You will have to make your class conform to the UICollectionViewDelegateFlowLayout. Now implement the function -
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize{
return CGSize(width: collectionView.bounds.size.width/numberOfColumns, height: collectionView.bounds.size.width/numberOfColumns);
}
Here 5 can be changed to the number of columns you want in each row and passing same value as height will ensure that the shape is always square. And hence you can apply corner radius to make it circular.
Now moving on. From your interface builder, ctrl + click and drag the UICollectionView height constraint to your UIViewController (similar to how you would do for a UIView but do it for the constraint)
Now once you know the number of items that you need to display inside your UICollectionView, you can do something like:
//replace 5 with the number of columns you want
//array contains the items you wish to display
func figureOutHeight(){
if(array.count == 0){
//as no items to display in collection view
return
}
//ceil function is just to round off the value to the next one, for example 6/5 will return 1 but we need 2 in this case. Ensure all arguments inside ceil function are float
let rowsCount = ceil(Float(array.count)/Float(numberOfColumns))
//now you have number of rows soo just update your height constraint by multiplying row count with height of each item inside UICollectionView
collectionViewHeightConstraint.constant = rowsCount * collectionView.bounds.size.height / numberOfColumns;
view.layoutIfNeeded()
}
Also if you haven't change the scroll direction to vertical.

Related

UICollectionView not displaying correct number of items

I'm making a calendar app and naturally, I want there to be 7 days in a week. This is how it currently displays with 5 days in a week:
Here is my storyboard. I've made sure that there is room for 7 items to fit the way I want them to. One thing that could be related to my problem is when I add more items (cells), they aren't of the same identifier (Calendar) as the first one.
And here is my code for the view controller:
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource{
#IBOutlet weak var Calendar: UICollectionView!
#IBOutlet weak var MonthLabel: UILabel!
let Months = ["January","February","March","April","May","June","July",
"August","September","October","November","December"]
let DaysOfMonth = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday","Sunday"]
let DaysInMonths = [31,28,31,30,31,30,31,31,30,31,30,31]
var currentMonth = String()
override func viewDidLoad() {
super.viewDidLoad()
currentMonth = Months[month]
MonthLabel.text = "\(currentMonth) \(year)"
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return DaysInMonths[month]
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Calendar", for: indexPath) as! DateCollectionViewCell
cell.backgroundColor = UIColor.clear
cell.DateLabel.text = "\(indexPath.row + 1)"
return cell
}
}
So to recap: I want there to be 7 items in a row in my collection view, but there are currently 5. I have tried changing the spacing in the storyboard, as well as the identifier for the Collection View Cell.
Try specifying the size for item at indexPath, to do that make sure you have set self.calendar.delegate = self and then add an extension to your ViewController
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.calendar.bounds.size.width / 7, height: your_cell_height)
}
}
This will work assuming you haven't specified any spacing between the cells, if you have specified spacing between the cells make sure you account it as well in your calculation.
For example: if you want spacing between cells (horizontally) be 10 calculation will change to
(self.calendar.bounds.size.width / 7) - 60) why 60? ( 6 spaces between 7 cells each of size 10 so 6 * 10)
Finally, why is it showing 7 cells in XIB and not in real device/ simulator? XIB's screen width might be different from the device screen width, you can drag Xib to required width to accommodate as many cells as you want horizontally, that does not guarantee that when app runs in real device it gets the similar real estate.
Its a flow layout, so content will flow, when it realizes there is no more space to accommodate more cells in a row, content (cell) flows to next row hope that clarifies the issue
P.S: Never name your variables with Pascal casing (first letter being capital) example #IBOutlet weak var Calendar: UICollectionView! always use Camel casing change it to #IBOutlet weak var calendar: UICollectionView!

How do I create a 'sticky' horizontal scrolling effect with collection view Swift 3

I have a collection view with multiple cells. Users can scroll horizontality. However, I want it to center the nearest cell once the user lets go. Or, if not, have some form of paging but for individual cells. I'm making basically an icon/profile picture picker.
#IBOutlet weak var profilePicScrollView: UIScrollView!
var profilePicsArray = [#imageLiteral(resourceName: "sample_user_photo"), #imageLiteral(resourceName: "settings_icon"), #imageLiteral(resourceName: "usernametextfield_dark")]
#IBOutlet weak var profilePicCollectionView: UICollectionView!
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return profilePicsArray.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionat section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageViewCellReuseID", for: indexPath) as! SettingsCollectionViewCell
cell.imageInCollection.image = profilePicsArray[indexPath.row]
cell.imageInCollection.backgroundColor = UIColor.getRandomColor()
return cell
}
Assuming your collection view's cells are equal to the width of your collection view's bounds, you could just set the collection view's isPagingEnabled property to true – considering UICollectionView inherits from UIScrollView.
If that's not the case, then you might try the following:
First, implement the scrollViewDidEndDragging(_:willDecelerate:) method of your collection view's delegate. Then, in that method's body, determine which cell in collectionView.visibleCells is most visible by comparing each of their centers against your collection view's center. Once you find your collection view's most visible cell, scroll to it by calling scrollToItem(at:at:animated:).

How to prevent dequeued collection view cells from overlapping each other?

I've been trying to set up a collection view where I have the user submit several strings which I toss in an array and call back through the collection view's cellForItemAt function. However, whenever I add a row to to the top of the collection view, it adds the cell label literally on top of the last cell label so they stack like this. Notice how every new word I add includes all the other previous words in the rendering.
The code I have at cellForItemAt is
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "InterestsCell", for: indexPath) as? InterestsCell {
cell.interestLabel.text = array[indexPath.row]
return cell
} else {
return UICollectionViewCell()
}
}
and the code I have when the add button is pressed is
func addTapped() {
let interest = interestsField.text
array.insert(interest!, at: 0)
interestsField.text = ""
collectionView.reloadData()
}
I'm not sure what's going on. I looked everywhere and tried to use prepareForReuse() but it didn't seem to work. I later tried deleting cells by calling didSelect and the cells would not disappear again. Any help is appreciated!
This is the code I have in my custom collection view cell implementation in the event that this is causing the error
To do this paste these functions in your project
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 1
}
you can play around with the values :)
This can be easily implemented using UITableView. So try to use UITableView instead of UICollectionView.
It looks like you're adding a new label every time you set the text. If you want to lazily load the UILabel you need to add lazy
lazy var interestLabel: UILabel = {
...
}()
I wouldn't do it this way though, I would create a reference to the label
weak var interestLabel: UILabel!
Then add the label in your setupViews method
func setupViews() {
...
let interestLabel = UILabel()
...
contentView.addSubview(interestLabel)
self.interestLabel = interestLabel
}

List of Images like Instagram

I want to make a scrollable list of images like instagram with 4 columns. I created a collection view with image views http://prntscr.com/d15rnx . But I get this result - http://prntscr.com/d15tsq
code -
// MARK: - UICollectionViewDataSource protocol
// tell the collection view how many cells to make
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.items.count
}
// make a cell for each cell index path
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! MyCollectionViewCell
// Use the outlet in our custom class to get a reference to the UILabel in the cell
cell.imageView.image = UIImage(named: "main/p\(self.items[indexPath.item].code)/main/main.jpg")
print("main_card_images/p\(self.items[indexPath.item].code)/main/main")
return cell
}
// MARK: - UICollectionViewDelegate protocol
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle tap events
print("You selected cell #\(indexPath.item)!")
print(self.items[indexPath.item])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let yourNextViewController = (segue.destination as! ViewControllerCard)
let indexPath = collectionView.indexPath(for: sender as! UICollectionViewCell)
yourNextViewController.mainCardImage = self.items[(indexPath?.item)!]
}
Make sure you have set inter cell spacing to 0 and then all you have to do is implement:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return(collectionView.bounds.size.width/4,collectionView.bounds.size.height/4)
}
This will resize all cells to be equal to 1/4 the UICollectionView width and thus create 4 cells in each row(1/4 the height value is just cause i like symmetry in these types of UI). If there appears to be any less number of cells than that, its because the inter-item spacing has not been set to 0. If you want inter-item spacing, just subtract that value from the width value returned in the above function
Alternatively
You can implement your own custom flow layout but the above solution is far more simpler.
I have found it easiest to configure my collection view via the setting in Interface Builder. For a collection view with a flow layout here are the settings I use for the insets. I have found the item size width: 125.8 and height: 125.8 to give me the best size for displaying on most devices in portrait or landscape.
Here's what my collection view looks like.
You can of course set this programmatically using UICollectionViewDelegateFlowLayout or as Rikh suggests by subclassing CollectionViewFlowLayout

How do I create a horizontal scrolling UICollectionView in Swift?

How can I make a horizontal scrolling collectionView that fills up cells going across the rows rather than down the columns?
I want there to 5 columns and 3 rows but when there is more than 15 items I want it to scroll to the next page. I'm having a lot of trouble getting this going.
Where you have a reference to your UICollectionViewFlowLayout(), just do:
layout.scrollDirection = .horizontal
Here is a nice tutorial for more info: https://www.youtube.com/watch?v=Ko9oNhlTwH0
Though for historical purposes, consider searching StackOverFlow quickly to make sure this isn't a duplicate.
Hope this helps.
Update:
Your items will fill horizontally first and if there is not enough room within the collectionview going to the right, they will go to next row. So, start by increasing your collectionview.contentsize (should be larger the screen to enable scrolling) and then set your collectionview item (cell) size.
flowLayout.itemSize = CGSize(width: collectionView.contentSize.width/5, height: collectionView.contentSize.height/3)
Option 1 - Recommended
Use custom layouts for your collection view. This is the right way to do this and it gives you a lot of control over how you want your cells to fill the collection view.
Here is a UICollectionView Custom Layout Tutorial from "raywenderlich"
Option 2
This is more like a hackish way of doing what you want. In this method you can access your data source in an order to simulate the style you need. I'll explain it in the code:
var myArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]
let rows = 3
let columnsInFirstPage = 5
// calculate number of columns needed to display all items
var columns: Int { return myArray.count<=columnsInFirstPage ? myArray.count : myArray.count > rows*columnsInFirstPage ? (myArray.count-1)/rows + 1 : columnsInFirstPage }
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return columns*rows
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath)
//These three lines will convert the index to a new index that will simulate the collection view as if it was being filled horizontally
let i = indexPath.item / rows
let j = indexPath.item % rows
let item = j*columns+i
guard item < myArray.count else {
//If item is not in myArray range then return an empty hidden cell in order to continue the layout
cell.hidden = true
return cell
}
cell.hidden = false
//Rest of your cell setup, Now to access your data You need to use the new "item" instead of "indexPath.item"
//like: cell.myLabel.text = "\(myArray[item])"
return cell
}
Here is this code in action:
*The "Add" button just adds another number to myArray and reloads the collection view to demonstrate how it would look with different number of items in myArray
Edit - Group items into pages:
var myArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]
let rows = 3
let columnsInPage = 5
var itemsInPage: Int { return columnsInPage*rows }
var columns: Int { return myArray.count%itemsInPage <= columnsInPage ? ((myArray.count/itemsInPage)*columnsInPage) + (myArray.count%itemsInPage) : ((myArray.count/itemsInPage)+1)*columnsInPage }
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return columns*rows
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath)
let t = indexPath.item / itemsInPage
let i = indexPath.item / rows - t*columnsInPage
let j = indexPath.item % rows
let item = (j*columnsInPage+i) + t*itemsInPage
guard item < myArray.count else {
cell.hidden = true
return cell
}
cell.hidden = false
return cell
}
Specify the height of the collection view and cell size. More details below:
Set the constraints of the UICollectionView, pinning the edges. Be sure to specify the UICollectionView's height or constraints so it's clear the cells can only scroll horizontally and not go down to the next line. The height should be the same or slightly larger than the cell height you specify in step 2.
Implement the UICollectionViewDelegateFlowLayout delegate and sizeForItemAt method. Here's a sample sizeForItemAt implementation.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellWidth = 100
let cellHeight = 30
return CGSize(width: cellWidth, height: cellHeight)
}

Resources