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.
Related
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!
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-:
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:).
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.
I am trying to remove line breaks for the items within my section using the UICollectionViewFlowLayout .
At the moment I have
|header |
|0-0 0-1 0-2 0-3|
|0-4 0-5 |
|header |
|1-0 1-1 1-2 0-3|
|1-4 1-5 |
and I need :
|header |
|0-0 0-1 0-2 0-3| 0-4 0-5
|header |
|1-0 1-1 1-2 1-3| 1-4 1-5
so users can horizontally scroll . On iOS i resolve this by creating two nested collectionViews but on tvOS I am unable to replicate the solution because i am unable to focus on the inner cells .
after several tries I override the preferredFocusedView variable on the tableCell where the nested UICollectionView is :
override var preferredFocusedView: UIView? {
return moviesCollection
}
This behavior allows me to swipe horizontally between the inner elements of the collection but i cannot swipe vertically to change between table cells.
I am kind of lost ,Any kind of help would be greatly appreciated.
Thanks .
This can be solved very quickly with a custom UICollectionViewLayout
Subclass UICollectionViewLayout & override the following properties and methods
var collectionViewContentSize: CGSize
func prepare()
func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
Make sure to return the correct frame for each cell
Here is my working solution that also conveniently provides some properties of UICollectionViewFlowLayout (itemSize, minimumInteritemSpacing, minimumLineSpacing).
You might not need to change anything and just add it to your project.
https://gist.github.com/kflip/52ec15ddb07aa830d583856909fbefd4
Feel free to add scrollDirection support ... its scrolling in both directions already, but you might want to change if one section should be displayed as column instead of a row...
You could also add FlowLayout-like protocol functionalities if you need more customisations based on IndexPaths (e.g. CellSize)
Updated
Here's a GIF showing the end result:
Original
I've just created a test ViewController and this seems to work alright.
Basically, what I have there is a UICollectionView with a vertical flow layout. Each cell of that collectionView has it's own UICollectionView set with an horizontal flow layout.
The only incovenient for the "out of the behavior" is that the horizontal flow layour collection view does not "remember" the selected item when coming back to it but that can be solved with a little bit of playing around.
Hope it helps. Here's my code for it:
class ViewController: UIViewController, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
override var preferredFocusEnvironments: [UIFocusEnvironment] {
return [collectionView]
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(withReuseIdentifier: "ParentTestCell", for: indexPath)
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "TestHeader", for: indexPath)
}
}
// Parent cell - included in the vertical collection view & includes the horizontal collection view
class ParentTestCell: UICollectionViewCell, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
override var preferredFocusEnvironments: [UIFocusEnvironment] {
return [collectionView]
}
// MARK: - UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(withReuseIdentifier: "TestCell", for: indexPath)
}
}
// Child cell - included in the horizontal collection view
class TestCell: UICollectionViewCell {
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
coordinator.addCoordinatedAnimations({
if self.isFocused {
self.backgroundColor = .red
}
else {
self.backgroundColor = .blue
}
}, completion: nil)
}
}
And here's the storyboard structre:
Just a suggestion if it can help you.
Add tableView
Create Custom TableVeiwCell which contains collection view inside the cell with horizontal scrolling.
for each section you have, will become number rows for tableview
for items in row becomes number of items for the respective collection view.
A UICollectionView lay out its cells in a very specific way when using UICollectionViewFlowLayout, I'm not sure it's possible to make that kind of behaviour with a collectionview, whitout making your own UICollectionViewLayout. See this guide on how to make a UIColloctionView with a custom layout.
I would suggest trying to solve the problem, by using nested UITableViews. Make a vertical scrolling tableview, where every cell in the tableview has it's own horizontal scrolling tableview. The cells in the horizontal tableview, will be the actual cells that the user can select. See this guide on how to do this.
This should work in tvOS as well, since the focus engine should look through the cells subviews, and find the focusable ones. I hope it helps :)