How to get indexPath of collectionViewCell on Button click and Button is in Out sided from collectionviewCell. I am trying with sender and Superview. But it is not working.
#IBAction func btnCancel(_ sender: Any) {
if let cell = (sender as AnyObject).superview??.superview?.superview?.superview?.superview?.superview as? ImageShowinPopUpCell {
let indexPath = collectionview.indexPath(for: cell)
deleteDublicateImage(id: isNilValue(assignValue: dbImageDataModel![(indexPath?.row)!].id))
collectionview.reloadData()
if dbImageDataModel.count == 0
{
dismiss(animated: true, completion: nil)
}
}
}
import UIKit
class ViewController: UIViewController ,UICollectionViewDelegate&UICollectionViewDataSource&UICollectionViewDelegateFlowLayout{
#IBOutlet weak var collectionView: UICollectionView!
var currentIndexPath:IndexPath!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
collectionView.delegate = self
collectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = indexPath.row % 2 == 1 ? .blue : .red
return cell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
currentIndexPath = indexPath
}
#IBAction func indexPathAction(_ sender: UIButton) {
print("Current IndexPath",currentIndexPath)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return collectionView.bounds.size
}
}
Create custom cell with button, give button action programatically, give tag values for button & superView like the below.
Implement button action, you can able to get index like below.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.button.superview.tag = indexPath.section
cell.button.tag = indexPath.row
cell.button.addTarget(self, action: #selector(self.buttonAction), for: .touchUpInside)
return cell
}
func buttonAction(_ sender:UIButton) {
let indexPath = IndexPath(row: sender.tag, section: sender.superview!.tag)
}
Try This
you set your cell button in cellForItemAt indexPath like this
cell.btnCancel.tag = indexPath.row
cell.btnCancel.addTarget(self, action:#selector(btnCancel), for:.touchUpInside)
//And catch your value like this
#objc func btnCancel(sender: UIButton)
{
let index = [sender.tag] as IndexPath
let dict = self.yourarray[sender.tag] as! NSDictionary
}
You can assign a tag to button and retrieve indexpath from the tag in action of the button.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.button.tag = indexPath.row
cell.button.addTarget(self, action: #selector(buttonTapped(sender:)), for: UIControl.Event.touchUpInside)
return cell
}
#objc func buttonTapped(sender:UIButton){
let indexPath = Indexpath(row:sender.tag , section: 0)
let cell = collectionView.cellForItem(at:indexPath)
}
Hope this helps!
Related
I am new to iOS and I want to implement a UICollectionView inside a UITableView which can have multiple select/unselect in section 1 of the UITableview. And in section 2, only a single selection is allowed. And save the selection sate event if I dismiss the Viewcontroller and when I open it again, it must show the last selected cell as highlight.
I searched for tutorials but all of them don't mention to select/unselect the state of collection cell or saving state after dismissing view controller.
Can someone help to implement it?
Thank in advance!
Here is my code I do till now:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return clvData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "clvCell", for: indexPath) as! demoCollectionViewCell
cell.title.text = clvData[indexPath.item] as? String
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = .red
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = .white
}
you guys can also check my project here:
https://mega.nz/#!xRs0EQyQ
try this and let me know if you have any problem or if this solved your problem.
var arrSelectedIndex:[IndexPath] = []// store this array either in database by api or in local
var clvData:[String] = []// your data array
//get the arrSelectedIndex from default in viewDidLoad before reloading the table and collection.
override func viewDidLoad() {
super.viewDidLoad()
if let myArray = UserDefaults.standard.array(forKey: "selectedArray") as? [IndexPath] {
arrSelectedIndex = myArray
} else {
arrSelectedIndex = []
}
}
// and save the arrSelectedIndex in viewWillDisappear method
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
UserDefaults.standard.set(arrSelectedIndex, forKey: "selectedArray")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return clvData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "clvCell", for: indexPath) as! demoCollectionViewCell
cell.title.text = clvData[indexPath.item] as? String
if arrSelectedIndex.contains(indexPath) { // You need to check wether selected index array contain current index if yes then change the color
cell.backgroundColor = UIColor.red
}
else {
cell.backgroundColor = UIColor.white
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = .red
if !arrSelectedIndex.contains(indexPath) {// if it does not contains the index then add it
arrSelectedIndex.append(indexPath)
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = .white
if let currIndex = arrSelectedIndex.firstIndex(of: indexPath) {// if it contains the index then delete from array
arrSelectedIndex.remove(at: currIndex)
}
}
I have a collection view which is nested inside of a table view cell. The first cell in the collection view is a button which allows users to create a new list. Its width is smaller than that of the main lists cells. I recently changed the code around so that the collectionView delegate and dataSource methods are in the tableViewController instead of the table view cell, however now the first cells width is not changing as it did before moving the code. This picture shows:
If I press into the blank space it is registering that I clicked the plus cell.
The code for all the collectionView delegate and dataSource methods:
extension ProfileTableViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("the media: \(usersLists.count)")
return usersLists.count + 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "addNewListCell", for: indexPath) as! addNewListCell
cell.currentUser = currentUser
return cell
}else{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "listCell", for: indexPath) as! userListsCell
cell.layer.applySketchShadow()
cell.layer.cornerRadius = 20
cell.layer.masksToBounds = false
cell.currentUser = currentUser
cell.media = usersLists[indexPath.item-1]
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
if indexPath.item == 0 {
let size = CGSize(width: 80, height: 158)
return size
}else{
let size = CGSize(width: 158, height: 158)
return size
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.item == 0 {
//button
self.addListDelegate?.addNewItem()
}
}
I tested if changing any values in the size in sizeForItemAt would change the cell size and nothing changes in any cells
the table view methods which are in the ProfileTableViewController class and not the extension:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "profileCell", for: indexPath) as! profileCell
cell.selectionStyle = .none
return cell
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let tableViewCell = cell as? profileCell else { return }
tableViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: self, forRow: indexPath.row) //this line sets the collectionView delegate and datasouce so that I can have all of the collectionView methods in this class
}
and this is the code inside of the tableViewCell
protocol addListCollectionDelegate: class {
func addNewItem()
}
class profileCell: UITableViewCell, UICollectionViewDelegate {
#IBOutlet weak var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
collectionView.register(UINib(nibName: "usersLists", bundle: nil), forCellWithReuseIdentifier: "listCell")
collectionView.register(UINib(nibName: "newListCell", bundle: nil), forCellWithReuseIdentifier: "addNewListCell")
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func setCollectionViewDataSourceDelegate
<D: UICollectionViewDataSource & UICollectionViewDelegate>
(dataSourceDelegate: D, forRow row: Int) {
collectionView.delegate = dataSourceDelegate
collectionView.dataSource = dataSourceDelegate
collectionView.reloadData()
}
}
Does anyone know how to change the width of the first cell since sizeForItemAt is not changing the size? Thanks
You forgot to add this protocol: UICollectionViewDelegateFlowLayout
It is required when you want to return custom size for an item.
Also improve your code like below:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "addNewListCell", for: indexPath) as! addNewListCell
cell.currentUser = currentUser
return cell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "listCell", for: indexPath) as! userListsCell
cell.layer.applySketchShadow() // Write this line in cell class
cell.layer.cornerRadius = 20 // Write this line in cell class
cell.layer.masksToBounds = false // Write this line in cell class
cell.currentUser = currentUser
cell.media = usersLists[indexPath.item - 1]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
return indexPath.item == 0 ? CGSize(width: 80, height: 158) : CGSize(width: 158, height: 158)
}
Try to use UICollectionViewDelegateFlowLayout method. In Xcode 11 or later, you need to set Estimate Size to none from Xib.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout:
UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let padding: CGFloat = 170
let collectionViewSize = advertCollectionView.frame.size.width - padding
return CGSize(width: collectionViewSize/2, height: collectionViewSize/2)
}
I have a collection view where users can select multiple cells and send the array of index to a server for saving their selection.
everything is working except that when the collection view is created the selected items needs to be clicked two times in order to deselect them.
How can I solve this issue of double clicking ?
extension ThirdViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return categoryList.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! UserCategoryCollectionViewCell
cell.categoryLbl.text = categoryList[indexPath.row]
if Defaults.hasKey(.categoryListIndices) {
if (Defaults[.categoryListIndices]?.contains(indexPath.row))! {
cell.alpha = 0.1
cell.isSelected = true
} else {
cell.alpha = 1
}
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! UserCategoryCollectionViewCell
cell.alpha = 0.1
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let deselectedCell = collectionView.cellForItem(at: indexPath) as? UserCategoryCollectionViewCell
deselectedCell?.alpha = 1
print(deselectedCell?.isSelected)
collectionView.deselectItem(at: indexPath, animated: false)
}
}
first of all. add this to your viewDidLoad.
collectionView?.allowsMultipleSelection = true
and then add these two functions.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell=collectionView.cellForItem(at: indexPath)
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: [])
cell?.backgroundColor = Color.hexStringToUIColor(hex: "#F5A331")
let lbl = cell?.subviews[1] as! UILabel
lbl.textColor = Style.cellHighlightedColor
}
override func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
let cell=collectionView.cellForItem(at: indexPath)
collectionView.deselectItem(at: indexPath, animated: true)
cell?.backgroundColor = UIColor.white
let lbl = cell?.subviews[1] as! UILabel
lbl.textColor = UIColor.darkGray
}
This will do
I am using Swift4 and Xcode9.
I create my collectionView cells by using the delegate method below;
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mediaItemCell", for: indexPath) as! MediaItemCollectionViewCell
cell.mediaItemCellImageView.image = mediaPlayer?.item(at: indexPath.row)?.image
return cell
}
Then, I want to replace the image of selected cell with a "stop" image. To achieve this, I have tried to use didSelectItemAt method;
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if (mediaPlayer?.isPlaying == true) {
// stop the player
mediaPlayer?.stopPlayingMedia()
} else {
// change the image and start playing
let cell = collectionView.cellForItem(at: indexPath) as! MediaItemCollectionViewCell // CRASH
cell.mediaItemCellImageView.image = UIImage(named: "stop.png")
...
...
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
// restore the original image
let cell = collectionView.cellForItem(at: indexPath) as! MediaItemCollectionViewCell
cell.mediaItemCellImageView.image = mediaPlayer?.item(at: indexPath.row)?.image
// stop playing
mediaPlayer?.stopPlayingMedia()
}
Now, when I select the cell which is already selected (hence the original image has already been replaced with "stop.png") the code above works; i.e. the original image is restored, which is done by stopPlayingMedia() method.
When I select a cell different than the currently selected one, the App crashes. I tried to move the didSelectItemAt call out of the delegate method, but it did not work.
Surprisingly, the logical check if collectionView.cellForItem(at: indexPath) is MediaItemCollectionViewCell succeeds when I select the currently selected cell, and fails when I select another cell.
I want to change the image of the cell, hence need to cast the variable type but it fails. Why does to cast fail when I select a different cell than the currently selected one?
Thanks
Edit:
I use collectionView.reloadData() to restore all the images to originals (I mentioned this in the OP - when stopPlayingMedia() is invoked). It appears that, if I add a private var index:IndexItem? to my ViewController class and update its value within didSelectItemAt (to save the last selected cell) and use the code below, the code does not crash
extension ViewController:MediaPlayerControllerDelegate {
// this delegate method is invoked when stopPlayingMedia() is called
func mediaPlayerControllerDidFinishPlaying() {
// restore the UI
// collectionView.reloadData() -> this causes the crash, instead, I only restore one cell
let cell = collectionView.cellForItem(at: index!) as! MediaItemCollectionViewCell
cell.mediaItemCellImageView.image = mediaPlayer?.item(at: (index?.row)!)?.image
timeLabel.text = "00:00"
progressBar.progress = 0.0
}
}
I tried the following to create cell and changing the image inside cell on selection. Not crashing at all. Please check the implementation, It might help you.
import UIKit
class ViewController: UIViewController {
private var isPlayingModel = [false, false, false, false, false] // initially all are stop, not playing
#IBOutlet weak var collectionView: UICollectionView! {
didSet{
collectionView.delegate = self
collectionView.dataSource = self
}
}
}
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return isPlayingModel.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mediaItemCell", for: indexPath) as! MediaItemCollectionViewCell
cell.imageVw.image = isPlayingModel[indexPath.row] ? #imageLiteral(resourceName: "start") : #imageLiteral(resourceName: "stop")
return cell
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
isPlayingModel[indexPath.row] = !isPlayingModel[indexPath.row]
let cell = collectionView.cellForItem(at: indexPath) as! MediaItemCollectionViewCell
cell.imageVw.image = isPlayingModel[indexPath.row] ? #imageLiteral(resourceName: "start") : #imageLiteral(resourceName: "stop")
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.size.width, height: 60)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 4
}
}
class MediaItemCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imageVw: UIImageView!
}
Check the implementation here.
You are not passing indexPath correctly to load a cell, that's why it is crashing.Edit your didSelectItemAt: method as follows:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedCell = IndexPath(item: indexPath.item, section: indexPath.section)
let cell = collectionView.cellForItem(at: selectedCell) as! ACollectionViewCell
cell.imgView.image = UIImage(named: "3.jpg")
}
Collection view indexPath has element item, and not row
Try this and see: (let me know what does it print)
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
// restore the original image
if let cell = collectionView.cellForItem(at: indexPath) as? MediaItemCollectionViewCell {
if let cellImage = mediaPlayer?.item(at: indexPath.item)?.image { // Collection view indexPath has element `item`, and not `row`
cell.mediaItemCellImageView.image = cellImage
} else {
print("cellImage not found at indexPath - \(indexPath)")
}
} else {
print("collection view cell `MediaItemCollectionViewCell` not found at indexPath -\(indexPath) ")
}
// stop playing
mediaPlayer?.stopPlayingMedia()
}
I have a TableView and in that UITableViewCell I have a UICollectionView.
Now in my collectionView For each collectionViewCell there is a create cell and remove cell button option. So for create cell I have to go for CreateViewController and for delete button I have to delete respective collectionViewCell.
import UIKit
class TableCollectionCell: UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
//collectionView cell
collectionView!.register(UINib(nibName: "CreateGroupCell", bundle: nil), forCellWithReuseIdentifier: "CreateGroupCell")
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
//Collection View
extension TableCollectionCell : UICollectionViewDataSource ,UICollectionViewDelegateFlowLayout
{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CreateGroupCell", for: indexPath) as! CreateGroupCell
cell.groupName.text = ""
CreateCardView(viewCorner: cell.cardView)
if(0 == indexPath.row)
{
//cell.btnDotted.tag = indexPath.row
//cell.btnShare.tag = indexPath.row
cell.btnDotted.setImage(UIImage(named: "Info"), for: UIControlState.normal)
cell.btnShare.setImage(UIImage(named : "AddGroup"), for: UIControlState.normal)
}
else if(indexPath.row >= 1 && indexPath.row <= 3)
{
cell.btnDotted.isHidden = true
cell.btnShare.isHidden = true
cell.groupImage.image = UIImage(named: "group_dull_card.png")
}
else
{
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if ( 0 == indexPath.row) {
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 200, height: 150)
}
}
Now in my didSelectRow method if (indexPath.row == 0) then I have to go for CreateViewController.
How can I do that? I am new in delegate also.
didSelectRow method
if ( indexPath.row == 0)
{
self.storyboard?.instantiateViewControllerWithIdentifier("CreateViewControllerid") as! CreateViewController.
self.navigationController?.pushViewController(secondViewController, animated: true)
}