In Table view we can put checkmark easily on cells.
But in Collection View how can we put check mark, when we select a cell (image)?
I just took a image view inside the cell and image view and put a tick mark image. My code is below.
But it's not working.
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
{
// handle tap events
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! customCollectionViewCell
if(cell.checkMarkImage.hidden == true)
{
print("Hidden")
cell.checkMarkImage.hidden = false
}
else
{
cell.checkMarkImage.hidden = true
print("No Hidden")
}
}
//Delegate Method cellForItemAtIndexPath
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) ->
UICollectionViewCell
{
//Get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(
"pickSomecell",
forIndexPath: indexPath) as! pickSomeGridViewController
//Show Images in grid view
cell.cellImage.image = self.arrAllOriginalImages[indexPath.row]
as? UIImage
//Check Mark toggle.
cell.toggleSelected()
//return cell.
return cell
}
And in pickSomeGridViewController show checkMark image selected or not.
class pickSomeGridViewController: UICollectionViewCell{
//Outlet of cell image.
#IBOutlet var cellImage: UIImageView!
//Outlet of checkMark image.
#IBOutlet var cellCheckMarkImage: UIImageView!
//Function for select and deselect checkmark.
func toggleSelected ()
{
//If image is selected.
if (selected)
{
//Show check mark image.
self.cellCheckMarkImage.hidden = false
}
else
{
//Hide check mark image.
self.cellCheckMarkImage.hidden = true
}
}
}
I see two main problems with this code:
You use dequeueReusableCellWithReuseIdentifier method which obtains different cell from collection view cache, not the one on screen.
Use cellForItemAtIndexPath method of collection view instead.
You try to save cell's state (selected/not selected) in the cell itself. It's common mistake when working with UITableView/UICollectionView and this approach will not work. Instead, keep the state in some other place (in dictionary, for example) and restore it every time collection view calls your data source cellForItemAtIndexPath method.
var arrData = NSMutableArray()
// 1.Make a ModalClass.swift and NSArray with modal class objects like this
class CustomModal: NSObject {
//Declare bool variable for select and deselect login
var is_selected = Bool()
//you can declare other variable also
var id = Int32()
}
// 2. custom array with modal objects
override func viewDidLoad() {
super.viewDidLoad()
let arrTemp = NSArray()
arrTemp = [1,2,3,4,5,6,7,8,9,10]
for i in 0 ..< arrTemp.count{
let eventModal = CustomModal()
eventModal.is_selected = false
eventModal.id = arrTemp[i]
arrData.add(eventModal)
}
tblView.reloadData()
}
// 2. Use collection view delegate method
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let modal = arrData[indexPath.row] as! CustomModal()
modal.is_selected = true
self.arrData.replaceObject(at: indexPath.row, with: modal)
tblView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let modal = arrData[indexPath.row] as! CustomModal()
modal.is_selected = false
self.arrData.replaceObject(at: indexPath.row, with: modal)
tblView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! YourCellClass
let modal = arrData[indexPath.row] as! CustomModal
if modal.is_selected == true{
cell.imgView.image = UIImage(named:"selected_image")
}else{
cell.imgView.image = UIImage(named:"deselected_image")
}
}
#Kishor, paintcode is the third party tool through which you can do that. I have provided the link too. since by default you don't have this facility, you should make your custom behavior to achiever this. Thanks.
Swift 4
In ViewController
// make a cell for each cell index path
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YourCollectionViewCellID", for: indexPath as IndexPath) as! YourCollectionViewCell
cell.someImageView.image = imgArr[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.item)!")
let cell = collectionView.cellForItem(at: indexPath) as? YourCollectionViewCell
cell?.isSelected = true
cell?.toggleSelected()
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as? YourCollectionViewCell
cell?.isSelected = false
cell?.toggleSelected()
}
In YourCollectionViewCell
class YourCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var someImageView: UIImageView!
#IBOutlet weak var checkImageView: UIImageView!
//Function for select and deselect checkmark.
public func toggleSelected() {
if (isSelected == false) {
//Hide check mark image.
self.checkImageView.image = UIImage(named: "unCheckImage")
isSelected = true
}else{
//Show check mark image.
self.checkImageView.image = UIImage(named: "CheckImage")
isSelected = false
}
}
}
Hope enjoy!!
var selectedCellIndex:Int?
take variable if you want to show selected Item after reloadData() : which is previously selected CellItem. {inspired by above answer }
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ColorCollectionCell", for: indexPath) as! ColorCollectionCell
cell.isSelected = false
if selectedCellIndex == indexPath.item {
cell.checkMarkImgView.image = UIImage(named: "icn_checkMark")
}else {
cell.toggleSelected()
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! ColorCollectionCell
cell.isSelected = true
selectedCellIndex = indexPath.item
cell.toggleSelected()
}
In CollectionViewCell u can use this method
class ColorCollectionCell: UICollectionViewCell {
#IBOutlet weak var cellimgView: UIImageView!
#IBOutlet weak var checkMarkImgView: UIImageView!
func toggleSelected() {
if (isSelected) {
self.checkMarkImgView.image = UIImage(named: "icn_checkMark")
}else{
self.checkMarkImgView.image = UIImage(named: "")
// here you can use uncheck img here i am not using any image for not selected.
}
}
}
Related
I am using a UICollectionView inside UITableViewCell. I am able to select the cells inside the UICollectionView. But when i try to get the UICollectionView or selected cells, the result is always null.I have been stuck on this for a long time. i included my code below for your reference.
class WeekDaysSelCell: UITableViewCell,UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
var weekdays = ["S", "M", "T", "W", "T", "F", "S"]
var weekdaysSelected = [String]()
#IBOutlet var weeklyDaysColView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
self.weeklyDaysColView.delegate = self
self.weeklyDaysColView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 7
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell : WeekDaysCollCell = weeklyDaysColView.dequeueReusableCell(withReuseIdentifier: "weekday", for: indexPath) as! WeekDaysCollCell
cell.weekDayLabel.text = weekdays[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell : WeekDaysCollCell = self.weeklyDaysColView.cellForItem(at: indexPath) as! WeekDaysCollCell
if (cell.backgroundColor == UIColor.gray) {
cell.backgroundColor = UIColor.clear
weekdaysSelected.removeAll { $0 == String(indexPath.row)}
//print("Removed from weekdaysSelected:", indexPath.row)
} else {
cell.backgroundColor = UIColor.gray
cell.isSelected = true
//weeklyDaysColView.selectItem(at: indexPath, animated: true, scrollPosition: [])
weekdaysSelected.append(String(indexPath.row))
//print("Added to weekdaysSelected:", indexPath.row)
}
}
}
// Trying to get the collection view from inside a willMove(toParent parent: UIViewController?) method.
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
if parent == nil
{
if let delegate = self.delegate {
print("Inside If condition")
// Code that i use to get the cell
let cell3 = tableView.dequeueReusableCell(withIdentifier: "cell3") as! WeekDaysSelCell
print(cell3.weekdaysSelected)
print(cell3.weeklyDaysColView.indexPathsForSelectedItems)
// Trying to pass selected cells
//delegate.repeatCustomSelection(selectedIdx: String(lastSelection.row),repeatCustomSel: repeatCustomSelection)
}
}
}
You are trying to get a reusable cell in willMove(toParent parent: UIViewController?) , this is not going to return you a expected cell.
You need to get the cell , using a indexPath .
func cellForRow(at indexPath: IndexPath) -> UITableViewCell?
#andyPaul, is right you are generating the new cell in willMove(toParent parent: UIViewController?). Instead of that you have to pass the indexpath pf collection view when ever user selected any cell to your controller from the tableView Cell Class.
Now What is TypeAlias you can read from this link about type alias:- https://www.programiz.com/swift-programming/typealias
Create the typeAlias on above of your tableViewCell Class like this:-
typealias closureBlock = (_ isCapture : AnyObject?) ->()
class tableViewCellClass: UITableViewCell {
var callBack: closureBlock?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
Just Go to CollectionView didSelectItemAt Method and use this code after your coding
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell : WeekDaysCollCell = self.weeklyDaysColView.cellForItem(at: indexPath) as! WeekDaysCollCell
if (cell.backgroundColor == UIColor.gray) {
cell.backgroundColor = UIColor.clear
weekdaysSelected.removeAll { $0 == String(indexPath.row)}
//print("Removed from weekdaysSelected:", indexPath.row)
} else {
cell.backgroundColor = UIColor.gray
cell.isSelected = true
//weeklyDaysColView.selectItem(at: indexPath, animated: true, scrollPosition: [])
weekdaysSelected.append(String(indexPath.row))
//print("Added to weekdaysSelected:", indexPath.row)
}
guard let callBackClosure = self.callBack else {
return
}
callBackClosure(indexPath as AnyObject)
// You can pass any value here either indexpath or Array.
}
}
Now you have to initialise this closure so that it can check whether in which controller it will return the value when you assign the value from CollectionView didSelectItemAt Method.
Go to your ViewController Class where you have added the tableview and their datasources.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//You have to pass your tableview Cell Instance here and their reuse Identifier
let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCellClass", for: indexPath) as! tableViewCellClass
cell.callBack = { [weak self] (selectedIndexPath) -> ()in
// You will get the current selected index path of collection view here, Whenever you pass any index path from collectionView did SelectItem Method.
print(selectedIndexPath)
}
return cell
}
I have UICollectionView 2 rows 10+ cells.
deselected by default. when I click it becomes selected but when I click again not deselect.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath)
let cell = collectionView.cellForItem(at: indexPath)
let collectionActive: UIImageView = {
let image=UIImageView(image: #imageLiteral(resourceName: "collectionActive"))
image.contentMode = .scaleAspectFill
return image
}()
let collectionInactive: UIImageView = {
let image=UIImageView(image: #imageLiteral(resourceName: "collectionInactive"))
image.contentMode = .scaleAspectFill
return image
}()
if cell?.isSelected == true {
cell?.backgroundView = collectionActive
}else{
cell?.backgroundView = collectionInactive
}
}
how fix that problem?
in viewDidLoad()
collectionView.allowsMultipleSelection = true;
afterword I implemented these methods
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! MyCell
cell.toggleSelected()
}
func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! MyCell
cell.toggleSelected()
}
finally in my class
class MyCell : UICollectionViewCell {
func toggleSelected ()
{
if (selected){
backgroundColor = UIColor.redColor()
}else {
backgroundColor = UIColor.whiteColor()
}
}
}
For Swift 5 +
in viewDidLoad()
collectionView.allowsMultipleSelection = true
afterword I implemented these methods
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! MovieDetailsDateCollectionViewCell
cell.toggleSelected()
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! MovieDetailsDateCollectionViewCell
cell.toggleSelected()
}
In TableView Cell class
class MyCell : UICollectionViewCell {
func toggleSelected ()
{
if (isSelected){
backgroundColor = .red
}else {
backgroundColor = .white
}
}
}
If you don't want to enable multiple selection and only want one cell to be selected at a time, you can use the following delegate instead:
If the cell is selected then this deselects all cells, otherwise if the cell is not selected, it selects it as normal.
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
let cell = collectionView.cellForItem(at: indexPath) as! CustomCell
if cell.isSelected {
collectionView.selectItem(at: nil, animated: true, scrollPosition: [])
return false
}
return true
}
According to UICollectionView class doc, you can use:
var selectedBackgroundView: UIView? { get set }
You can use this view to give the cell a custom appearance when it is selected. When the cell is selected, this view is layered above the backgroundView and behind the contentView.
In your example in the cellForItem(at indexPath: IndexPath) -> UICollectionViewCell? function you can set:
cell.backgroundView = collectionInactive
cell.selectedBackgroundView = collectionActive
If the cell is selected, just set cell.isSelected = false in shouldSelectItemAt delegate and in a DispatchQueue.main.async { } block. So the state is actually changed to false (very) soon after the shouldSelectItemAt has been executed.
It may look like a hack but it actually works.
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
if let cell = collectionView.cellForItem(at: indexPath), cell.isSelected {
DispatchQueue.main.async { // change the isSelected state on next tick of the ui thread clock
cell.isSelected = false
self.collectionView(collectionView, didDeselectItemAt: indexPath)
}
return false
}
return true
}
Please let me know if you find/know any cons to do this. Thanks 🙏
In iOS 14 and newer, you can set the backgroundConfiguration property of a cell. Once set, all necessary visual effects for selecting and deselecting works automatically. You can use one of the preconfigured configurations, like this:
cell.backgroundConfiguration = .listSidebarCell()
…or create a UIBackgroundConfiguration object from scratch. You can also change a preconfigured configuration before applying.
More info here: https://developer.apple.com/documentation/uikit/uibackgroundconfiguration
I have a UICollection view of things. these things can have 3 states:
- Active
- Neutral
- Inactive
Now, here is the code for the UICollectionViewCell:
class NGSelectStashCell: UICollectionViewCell {
var status: String = "Active"
#IBOutlet weak var statusImage: UIImageView!
#IBOutlet weak var bgImage: UIImageView!
#IBOutlet weak var titleLabel: UILabel!
func changeStatus()
{
switch status {
case "Active":
status = "Neutral"
//change bgImage
case "Neutral":
status = "Inactive"
//change bgImage
case "Inactive":
status = "Active"
//change bgImage
default:
print("No Status")
}
}
}
Now, when I declare the UICollection View, I want to make it so that when the user "clicks" on the UICell it will call out the changeStatus() function. How can I do this in the Delegate/DataSource code?. Also, how do I save the "status" of the each cell (so that if I refresh the UICollectionView they don't all return to "active" ?
/*
////////// UICOLLECTIONVIEW FUNCTIONS ///////////
*/
extension NewOnlineGameVC: UICollectionViewDelegate, UICollectionViewDataSource
{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return availableStashes.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let stashCell = collectionView.dequeueReusableCell(withReuseIdentifier: "ngStashCell", for: indexPath) as! NGSelectStashCell
stashCell.titleLabel.text = availableStashes[indexPath.row]
// stashCell.bgImage make image file with the same name as the name and change bg image to it.
return stashCell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// code to toggle between active/neutral/inactive
// do I re-declare stashCell as! NGSelectStashCell? or what do I do?
}
}
Unfortunately the solution is a bit more complicated then you think. Collection views may queue and reuse their cells for performance gains. That means that a single cell may and will be used for multiple objects when scrolling. What will happen is that when you will change the state on first cell and will scroll so it is reused then this cell will preserve its state and will look as if another cell has this changed state...
So your source of truth must always be your data source. Whatever availableStashes contains it needs to also contain its state. So for instance if you currently have var availableStashes: [MyObject] = [] you can change it like this:
typealias MySource = (status: String, object: MyObject)
var availableStashes: [MySource] = []
func setNewObjects(objects: [MyObject]) {
availableStashes = objects.map { ("Neutral", $0) }
}
Now on press you need to update the object in your data source for instance:
func changeStatusOfObjectAtIndex(_ index: Int, to newStatus: String) {
availableStashes[index] = (newStatus, availableStashes[index].object)
}
So on press you do:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
changeStatusOfObjectAtIndex(indexPath.row, to: <#Your new status here#>)
UICollectionView().reloadItems(at: [indexPath])
}
This will now trigger a reload for this specific cell which you can now update like
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let stashCell = collectionView.dequeueReusableCell(withReuseIdentifier: "ngStashCell", for: indexPath) as! NGSelectStashCell
stashCell.dataObject = availableStashes[indexPath.row]
return stashCell
}
And inside the cell:
var dataObject: NewOnlineGameVC.MySource {
didSet {
titleLabel.text = dataObject.object
switch dataObject.status {
case "Active":
//change bgImage
case "Neutral":
//change bgImage
case "Inactive":
//change bgImage
default:
print("No Status")
}
}
}
I hope this clears your issue.
You can change the status of the cell once you get the reference of the selected cell.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? NGSelectStashCell else {return}
cell.status = "Active"
cell.changeStatus()
}
If you want to save the status of the cell then it must be model driven i.e anything happens to the cell must be saved to the model and the same model have to be reflecte in the cell when collection view tries to reuse the previously instantiated cells.
You already have a model AvailableStash, lets use it in proper way.
struct AvailableStash {
var statusImage: UIImage?
var backgroundImage: UIImage?
var title: String?
var status: String
//Initilize properties properly
init(with status: String) {
self.status = status
}
}
Your collection view must be model driven. For eg:
class DemoCollectionView: UICollectionViewController {
var availableStashes: [AvailableStash]?
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return availableStashes?.count ?? 0
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let stashCell = collectionView.dequeueReusableCell(withReuseIdentifier: "ngStashCell", for: indexPath) as! NGSelectStashCell
let item = availableStashes[indexPath.row]
stashCell.titleLabel.text = item
// stashCell.bgImage make image file with the same name as the name and change bg image to it.
stashCell.statusImage = item.statusImage
return stashCell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? NGSelectStashCell else {return}
cell.status = availableStashes[indexPath.row].status
cell.changeStatus()
availableStashes[indexPath.row].status = cell.status
}
}
I am trying to change an UIImage in the collectionviewcell with the help of on didSelectItemAt. It is working when I select cell on the beginning but when I scroll down and scroll back up the images are back to what they were and changed images shift to different cells.
First cell selected Image
Scrolled down then came up Image
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collection_cell", for: indexPath) as! RateCardCollectionViewCell
let tempString = "\(NSLocalizedString("base_url", comment: ""))\(itemImageLink[indexPath.row])"
let urlnew:String = tempString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
let url = URL(string: urlnew)
cell.rateCardImage.kf.setImage(with: url,placeholder: UIImage(named: "Junkart_Bin.png"))
cell.label.text = itemName[indexPath.row]
cell.itemRate.text = itemRate[indexPath.row]
cell.layer.cornerRadius = 3
cell.card.backgroundColor = UIColor.white
cell.card.layer.masksToBounds = false
cell.card.layer.shadowColor = UIColor.black.withAlphaComponent(0.2).cgColor
cell.card.layer.shadowOffset = CGSize(width: 0, height: 0)
cell.card.layer.shadowOpacity = 1 //0.8
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! RateCardCollectionViewCell
if self.selectedRate == nil {
cell.changeImagetoSelected()
self.selectedRate = [Int]()
self.selectedRate.append(indexPath.row)
}
else {
}
}
Following is the UICollectionViewCell Class
class RateCardCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var rateCardImage: UIImageView!
#IBOutlet weak var card: UIView!
#IBOutlet weak var itemRate: UILabel!
#IBOutlet weak var selected_rate_image: UIImageView!
func changeImagetounSelected() {
selected_rate_image.image = UIImage(named: "unselected_rate")
}
func changeImagetoSelected() {
selected_rate_image.image = UIImage(named: "selected_rate")
}
}
Please Help!!
Check the cell creation index added on the selectedRate at the time of cell creation cellForItemAt.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collection_cell", for: indexPath) as! RateCardCollectionViewCell
// Your remaining codes are here.
if selectedRate.contains(IndexPath.row) {
cell.changeImageToSelected()
} else {
cell.changeImageTounselected()
}
return cell
}
This happened because collection view dequeue his cells. So you need to persist your selection in dataModel and get if cell is selected or no from it. So persist your selectedIndex in your viewController and in your cellForItemAt method call:
if(self.selectedIndex == indexPath.row){
cell.changeImageToSelected()
}
else{
cell.changeImageTounselected()
}
This will persist your selection when scroll
I tried many days to realise this:
I want to add in my UIViewController two different CollectionView.
For example I want to put images in these collectionView
Each CollectionView use its own images.
Is this possible?
I will be very happy if somebody can give me a hand. :)
This is possible, you just need to add each UICollectionView as a subview, and set the delegate and dataSource to your UIViewController.
Here's a quick example. Assuming you have one UICollectionView working, you should be able to adapt this code to your own uses to add a second fairly easily:
let collectionViewA = UICollectionView()
let collectionViewB = UICollectionView()
let collectionViewAIdentifier = "CollectionViewACell"
let collectionViewBIdentifier = "CollectionViewBCell"
override func viewDidLoad() {
// Initialize the collection views, set the desired frames
collectionViewA.delegate = self
collectionViewB.delegate = self
collectionViewA.dataSource = self
collectionViewB.dataSource = self
self.view.addSubview(collectionViewA)
self.view.addSubview(collectionViewB)
}
In the cellForItemAtIndexPath delegate function:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if collectionView == self.collectionViewA {
let cellA = collectionView.dequeueReusableCellWithReuseIdentifier(collectionViewAIdentifier) as UICollectionViewCell
// Set up cell
return cellA
}
else {
let cellB = collectionView.dequeueReusableCellWithReuseIdentifier(collectionViewBIdentifier) as UICollectionViewCell
// ...Set up cell
return cellB
}
}
In the numberOfItemsInSection function:
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.collectionViewA {
return 0 // Replace with count of your data for collectionViewA
}
return 0 // Replace with count of your data for collectionViewB
}
Yes--this is entirely possible. You can either assign their respective UICollectionViewDelegates/UICollectionViewDataSources to different classes or subclass the CollectionViews, assigning both the delegate and data source to your current viewController and downcast your reference to collectionView in the delegation methods like so:
#IBOutlet collectionViewA: CustomCollectionViewA!
#IBOutlet collectionViewB: CustomCollectionViewB!
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if let a = collectionView as? CustomCollectionViewA {
return a.dequeueReusableCellWithIdentifier("reuseIdentifierA", forIndexPath: indexPath)
} else {
return collectionView.dequeueReusableCellWithIdentifier("reuseIdentifierB", forIndexPath: indexPath)
}
}
Subclass UICollectionView like this:
class CustomCollectionViewA: UICollectionView {
// add more subclass code as needed
}
class CustomCollectionViewB: UICollectionView {
// add more subclass code as needed
}
You can use the factory design pattern to build two different collection views and return them via functions. Here's my working version for swift 4.
This code goes in a separate helper file:
import UIKit
class collectionViews {
static func collectionViewOne() -> UICollectionView {
let layout = UICollectionViewFlowLayout()
let collectionViewOne = UICollectionView(frame: CGRect(x: 0, y: 20, width: 200, height: 100), collectionViewLayout: layout)
return collectionViewOne
}
static func collectionViewTwo() -> UICollectionView {
let layout = UICollectionViewFlowLayout()
let collectionViewTwo = UICollectionView(frame: CGRect(x: 0, y: 300, width: 200, height: 100), collectionViewLayout: layout)
return collectionViewTwo
}
}
And here is the view controller code:
import UIKit
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
let collectionViewOne = collectionViews.collectionViewOne()
let collectionViewTwo = collectionViews.collectionViewTwo()
var myArray = ["1", "2"]
var myArray2 = ["3", "4"]
override func viewDidLoad() {
super.viewDidLoad()
collectionViewOne.delegate = self
collectionViewOne.dataSource = self
collectionViewOne.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MyCell")
view.addSubview(collectionViewOne)
collectionViewTwo.delegate = self
collectionViewTwo.dataSource = self
collectionViewTwo.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MyCell2")
view.addSubview(collectionViewTwo)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.collectionViewOne {
return myArray.count
} else {
return myArray2.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.collectionViewOne {
let myCell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath as IndexPath)
myCell.backgroundColor = UIColor.red
return myCell
} else {
let myCell2 = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell2", for: indexPath as IndexPath)
myCell2.backgroundColor = UIColor.blue
return myCell2
}
}
}
Result
You can also name the collection views outlets differently (without subclassing):
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var SecondCollectioView: UICollectionView!
method:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath) as UICollectionViewCell
if(collectionView == self.SecondCollectioView) {
cell.backgroundColor = UIColor.black
} else {
cell.backgroundColor = self.randomColor()
}
return cell;
}
This is will be an another way.
Here's my working version for swift 5 and Xcode 11:
create outlets for corresponding collectionviews: outlets:
#IBOutlet weak var bgCollectionView: UICollectionView!
#IBOutlet weak var frontCollectionView: UICollectionView!
var arrImages = [String : [UIImage]]()
arrImages is contain like
override func viewDidLoad() {
super.viewDidLoad()
arrImages = [
"frontImg": [//Front UIImage array],
"bgImg": [//Background UIImage array]
]
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let arrImg = arrImages["bgImg"] {
return arrImg.count
} else if let arrImg = arrImages["frontImg"]{
return arrImg.count
}
return 0
}
You can do this two ways
Using CollectionView Outlets
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
if collectionView == self.bgCollectionView{
if let arrImg = arrImages["bgImg"]{
cell.imgView.image = arrImg[indexPath.row]
}
}else{
if let arrImg = arrImages["frontImg"]{
cell.imgView.image = arrImg[indexPath.row]
}
}
return cell
}
Using CollectionView Tag:
Here Background Images collectionview tag is 1 and Front Images collectionview tag is 2.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
if collectionView == collectionView.viewWithTag(1){
if let arrImg = arrImages["bgImg"]{
cell.imgView.image = arrImg[indexPath.row]
}
}else{
if let arrImg = arrImages["frontImg"]{
cell.imgView.image = arrImg[indexPath.row]
}
}
return cell
}
Please Add Tag in CollectionView Like this:
Thank You. Hope It's working for you !!
Swift 5 Answer!
If you try connecting both collectionViews to the same view controller Xcode will throw an error "Outlets cannot connect to repeating content"
Solution:
Head to Storyboard
Connect the first collectionView via outlet, set the delegate/dataSource in viewDidLoad and then add a tag to the second collectionView by heading to the attributes inspector in storyboard and change the value from 0 to 1
Select the secondCollectionView and go to the connections inspector and select delegate and drag the connection to the UIViewController and the same for the dataSource.
Simply check which collectionView is passing through.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == collectionView.viewWithTag(1) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "secondCollectionView", for: indexPath)
return cell
}
else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "firstCollectionView", for: indexPath) as! HomeMainCollectionViewCell
cell.configureCell()
return cell}
}