UICollectionView not displaying correct number of items - ios

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!

Related

Unscrollable Multiple Collection Views

I am trying to develop an iOS application that has 03 UICollectionViews handled by the same ViewController. (The reason why I am opting for 03 UICollectionViews rather than one with sections and different prototype cells is because I may need to add additional content not relevant to collection views between the sections in the future)
________________
| _ _
| | | | |
| |_| |_| . . . (UICollectionView1)
|_______________
_______________
| _ _
| | | | |
| |_| |_| . . . (UICollectionView2)
|_______________
My problem is as follows:
No of cells in each of the CollectionViews is variable and if the number exceeds the width constraint it wraps down (so far so good). However, the height constraint of the UICollectionView causes a scroll bar to appear rather than simply laying out the cells if the number of cells causes to wrap beyond the height constraint
I have tried a couple of things to get this to work, most of which revolve around the advice given in the following questions
how to set dynamic height of a Collection View, the 'view' not the 'cells'?
How to adjust height of UICollectionView to be the height of the content size of the UICollectionView?
In the end I tried this
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.contentSize.height)
}
But then the content in the UICollectionViewCell stretched wierdly and still problem of scrolling exists.
I do not mind that the main view of the view controller (the one on which all other UICollectionViews are placed) becomes scrollable (actually that is part of the requirement), I just don't want the UICollectionViews to act like some HTML iframe and allow scrolling but just layout the cells in order for as much as constrained by width of the UICollectionView
In pseudo code, something like this
array = (cell1, cell2, cell3)
for i in array
if currentCollectionViewRow is filled
wrapToNextLine()
add cell{i} to view controller
Any help is appreciated. Even help that suggests better ways to achieve this functionality along with best practices rather than hacking code
EDIT
I carried out the instructions as #Saad Chaudhry mentioned but to no avail. My layout is as follows: CollectionView Layout
As you can see, the stack view encloses both collection views as suggested. Now the IDE gives the following complaints: Ambiguous Layout
I tried adding constraints intuitively, then tried the IDEs options to add constraints automatically to no avail. Most times, there is no data cells on the screen.
For more information, without stack view but just two collection views, I get the following simulation: Without Stack Views
And with stack views I get the simulation: With stack views and note that the second collection view is missing
If I then constraint the stack view at 0,0,0,0, this brings back the issues where each of the collection view heights are ambiguous. Providing heights causes scrolling "within that collection view" if the number of cells are large (from datasource).
I simply want all the black squares to be rendered first and then the yellow squares. The parent view may scroll and that's fine bu not the individual collection views
My code for the controller:
import UIKit
class DemoCollectionViewController: UICollectionViewController {
#IBOutlet weak var nonPriorityCollectionView: UICollectionView!
#IBOutlet weak var priorityCollectionView: UICollectionView!
private let reuseIdentifier = "priorityCell"
fileprivate let sectionInsets = UIEdgeInsets(top: 50.0, left: 20.0, bottom: 50.0, right: 20.0)
fileprivate var priorityItems = [PriorityItem]()
fileprivate let itemsPerRow: CGFloat = 3
private let nonPriorityReuseIdentifier = "nonPriorityCell"
fileprivate var nonPriorityItems = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: nonPriorityReuseIdentifier)
loadPriorityItems()
loadNonPriorityItems()
}
func loadPriorityItems(){
let item1 = PriorityItem(image: #imageLiteral(resourceName: "User"))
let item2 = PriorityItem(image: #imageLiteral(resourceName: "User"))
let item3 = PriorityItem(image: #imageLiteral(resourceName: "User"))
let item4 = PriorityItem(image: #imageLiteral(resourceName: "User"))
priorityItems = [item1, item2, item3, item4, item1, item2, item3, item4]
}
func loadNonPriorityItems(){
let item1 = "Item 1"
let item2 = "Item 2"
let item3 = "Item 3"
let item4 = "Item 4"
nonPriorityItems = [item1, item2, item3, item4]
}
}
// MARK: UICollectionViewDataSource
extension DemoCollectionViewController {
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.priorityCollectionView {
return priorityItems.count
}
else{
return nonPriorityItems.count
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.priorityCollectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
cell.backgroundColor = UIColor.black
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: nonPriorityReuseIdentifier, for: indexPath)
cell.backgroundColor = UIColor.yellow
return cell
}
}
}
extension DemoCollectionViewController : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
let availableWidth = view.frame.width - paddingSpace
let widthPerItem = availableWidth / itemsPerRow
return CGSize(width: widthPerItem, height: widthPerItem)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAt section: Int) -> UIEdgeInsets {
return sectionInsets
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return sectionInsets.left
}
}
Please help me in figuring out this issue. I now feel like collection view is not the ideal way to achieve this due to the complexity I'm facing...

How to make pagination with UITableView along with UISegmentedControll

My view controller has a UISegmentedControll with 6 buttons/segment. It also has a UITableView under that Segmented Controll. If we switch segment by click then table view is reloading with different data. Now we are trying to implement a way where if user swipe the table view either in left-to-right or right-to-left, a pagination like behaviour will be appeared in the view controller. Means the table view will horizontally scrolled along with segmented controll. How can we implement this feature in my app in best approach ? My table has three different custom cell, one of which also has a UICollectionView inside the cell. Pls help me out. Thanks.
The idea I would suggest is to use tableView inside collectionView. Steps to follow-:
1) Add segmentedControl and collectionView on your controller.
2) Connect dataSource and delegate by control dragging from collectionView to controller yello icon.
3) Adjust your cell height.
4) Select collectionView, and go to attribute inspector. Look for-: 1) scroll direction and make horizontal. 2) Check Paging Enabled parameter.
5) Apply Constraints on views.
6) Give collectionView cell identifier.
7) Give your cell a custom collectionView cell class.
8) Connect all Outlets to respected views.
Controller class -:
import UIKit
class ViewController: UIViewController {
// OUTLETS
#IBOutlet weak var controls: UISegmentedControl!
#IBOutlet weak var horizontalCollectionView: UICollectionView!
// ARRAy OF TYPE UICOLOR
var collectionViewColors = [UIColor.gray,UIColor.green,UIColor.red,UIColor.yellow,UIColor.blue,UIColor.brown]
// ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
}
//didReceiveMemoryWarning
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Function to calculate cell index on scroll end.
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>){
let pagingIndex = targetContentOffset.pointee.x/self.view.frame.width
// Set segmented controll current index
controls.selectedSegmentIndex = Int(pagingIndex)
}
}
// Collection view methods
extension ViewController : UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout{
// number of section
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
// number of items in section
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return collectionViewColors.count
}
// deque cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collection", for: indexPath) as! HorizontalCell
cell.horizonatlColorsView.backgroundColor = collectionViewColors[indexPath.item]
return cell
}
// set minimum line spacing to zero.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
Custom cell class-:
import UIKit
class HorizontalCell: UICollectionViewCell {
// UIView outlet
#IBOutlet weak var horizonatlColorsView: UIView!
}
After setting up everything , scroll horizontally you will get the output you want.Also you can add tableView inside collectionView now.
Output-:

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:).

count which image user is on horizontal collection view, paging enabled

I have an array called imageArray = ["one", "two", ... "eight"]. And it fills a collection view with images. Preconditions: The cell is centered in the view and pretty much takes up the majority of the collectionview/view. The image view is on top and is just a little smaller than the cell. Ok so now I need to count which image the user is on. So if the user scrolls to image 3 I need a way to count 3. I am new to ios programming when it comes to collection views, just found out about them, and need some help.
Here is some code (basic setup)
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var myCollectionView: UICollectionView!
#IBOutlet var mainView: UIView!
var imageArray = ["one", "two", "three", "four", "five", "six", "seven", "eight"]
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! UserFeedCollectionViewCell
cell.myImage.image = UIImage(named: imageArray[indexPath.row])
cell.myImage.layer.cornerRadius = 12.0
cell.myImage.clipsToBounds = true
return cell
}
Solution
So when you wanted to know which cell is visible on the screen then
Use property visibleCells, it will gives you array of cells which is currently visible on screen.
Use property indexPathsForVisibleItems, it will gives you array of indexPath of all visible cells.
For info
In UICollectionView you are using dequeue property. The dequeue property works something like this :
let you have N cells and V cells are possible on the screen then it takes memory of V+1 cell and after scrolling it releases the memory from the early cell and give it to new visible cell.
Hope it helps you.
What you should do is this:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var aPoint = CGPoint()
collectionView.indexPathForItem(at: aPoint)
}
once you have the index for, let's say the center of the screen or UICollectionView, you can figure out the row and thus the image.

Dynamic UICollectionView in UIViewController

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.

Resources