Accessing indexPath in a collectionview - ios

I have a collectionview cell which has an image on it and a button below it. Now when I click on this button, I want to load a tableviewCell which has on it the image from the collectionview. To achieve this, I did this initially..
func SellBtnTapped(_ sender: UIButton) {
let indexPath = collectionView?.indexPath(for: ((sender.superview?.superview) as! RecipeCollectionViewCell))
self.photoThumbnail.image = self.arrayOfURLImages[(indexPath?.row)!]
and photoThumbnail is defined like so...var photoThumbnail: UIImageView! But doing this gives a crash telling 'Unexpectedly found nil while unwrapping an optional value' So I tried this..
let point = sender.convert(CGPoint.zero, to: self.collectionView)
let myIndexPath = self.collectionView.indexPathForItem(at: point)
self.photoThumbnail.image = self.arrayOfURLImages[(myIndexPath?.row)!]
But again, the same crash of Unexpectedly found nil.... is happening. Any idea as to what could be the issue..?
EDIT:
This is the code for cellForItemAtIndex...
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath as IndexPath) as! RecipeCollectionViewCell
cell.sellButton.tag = indexPath.item
cell.sellButton.addTarget(self,action: #selector(SellBtnTapped(_:)),for: .touchUpInside)
return cell
}

It's because you alway get nil your indexPath.
Another approach is
in collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath method
set tag of your cell's button like
cell.myButton.tag = indexPath.item
And in SellBtnTapped method use below code for get indexPath
let indexPath = NSIndexPath(item: sender.tag, section: 0) // set section as you want
let cell = collectionView.cellForItem(at: indexPath as NSIndexPath) as! RecipeCollectionViewCell
Now by use of cell you can get image object that is on it or use self.arrayOfURLImages to get right image. and do your further stuff.

I prefer avoiding tags altogether. I wrote this a while ago and still find it useful.
extension UIView {
var superCollectionViewCell: UICollectionViewCell? {
if let cell = self as? UICollectionViewCell {
return cell
} else {
return superview?.superCollectionViewCell
}
}
var superCollectionView: UICollectionView? {
if let collectionView = self as? UICollectionView {
return collectionView
} else {
return superview?.superCollectionView
}
}
var indexPathOfSuperCollectionViewCell: IndexPath? {
guard let cell = superCollectionViewCell, let collectionView = superCollectionView else { return nil }
return collectionView.indexPath(for: cell)
}
}
This turns your action into
func SellBtnTapped(_ sender: UIButton) {
guard let indexPath = sender.indexPathOfSuperCollectionViewCell else {
print("button has no index path")
return
}
self.photoThumbnail.image = self.arrayOfURLImages[indexPath.row]
}

Related

iOS swift: UICollectionview horizontal scroll single cell not reloading

In my application UICollection scroll horizontally. collection-view cell two button and one UIView was designed.
Each cell loaded two button. when user click one button the particular cell should be reload and the UIView will be displayed in that cell only other cell not displayed.
Here i attached the collectionview image:
Here my code:
#IBOutlet weak var dataCollectionView: UICollectionView!
var addIndexArray:[Int] = []
var arraySelectedFilterIndex = [IndexPath]()
self.listArr = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t"]
override func viewDidLoad() {
super.viewDidLoad()
self.dataCollectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return (lvsnListArr.count/2)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = dataCollectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier_CollectionView, for: indexPath as IndexPath) as! DataCell
if self.arraySelectedFilterIndex.contains(indexPath) {
cell.buttonView.isHidden = false
}else{
cell.buttonView.isHidden = true
}
cell.btn1.setTitle(lvsnListArr[2 * indexPath.row], for: .normal)
cell.btn1.tag = indexPath.row
cell.btn1.addTarget(self, action: #selector(btnPressed(sender:)), for: .touchUpInside)
cell.btn2.setTitle(lvsnListArr[2 * indexPath.row + 1], for: .normal)
cell.btn2.tag = indexPath.row
cell.btn2.addTarget(self, action: #selector(btn2Pressed(sender:)), for: .touchUpInside)
return cell
}
#objc func btnPressed(sender: UIButton) {
self.addIndexArray.append(sender.tag)
let hitPoint = sender.convert(CGPoint.zero, to: dataCollectionView)
if let indexPath = dataCollectionView.indexPathForItem(at: hitPoint) {
print("indexPath--->",indexPath)
self.arraySelectedFilterIndex.append(getIndexPath)
self.dataCollectionView.reloadItems(at: [getIndexPath])
}
}
#objc func btn2Pressed(sender: UIButton) {
self.addIndexArray.append(sender.tag)
let hitPoint = sender.convert(CGPoint.zero, to: dataCollectionView)
if let indexPath = dataCollectionView.indexPathForItem(at: hitPoint) {
print("indexPath--->",indexPath)
self.arraySelectedFilterIndex.append(getIndexPath)
self.dataCollectionView.reloadItems(at: [getIndexPath])
}
}
My error:
I clicked cell btn1 one action btnPressed single cell over load and display the next cell image both images are overlap in collectionview.
Here i attached my issue cell image.
Here i got cell indexpath and indexpath.row, how can i validate and fix this issue in cell for row. struggling this point.
Kinldy help to fix this issues. Thanks advance.
You can reload single cell like you are doing in button action
self.dataCollectionView.reloadItems(at: [getIndexPath])
OR
You can get cell in button action like as below
if let cell = self.dataCollectionView.cellForItem(at: indexPath) as? DataCell
{
//Here you can write your view hide show code
if self.arraySelectedFilterIndex.contains(indexPath)
{
cell.buttonView.isHidden = false
}
else
{
cell.buttonView.isHidden = true
}
}

How to use the button inside CollcetionViewCell to delete cell itself

My app use flickr Api to search photo and present by collectionView.
Also I want to add the photo that i want to favorite.
So I add the button on cell and use Coredata to save image and photo title.
#IBAction func saveBtn(_ sender: UIButton) {
checkFavorite(photoTitle: photoArrayFromSearchView[sender.tag].title)
if isAdded == false {
save(btnTag: sender.tag)
}
}
func save(btnTag:Int){
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let favoritePhotos = FavoritePhotos(context: context)
downloadFavoriteData(btnTag: btnTag, favoritePhotos: favoritePhotos) { (dataDownloaded) in
if dataDownloaded {
do {
try context.save()
print("save successfully")
} catch {
print("fail to save")
}
}
}
}
I cant find the way to press button to save relative photo so i use button.tag = cell.item to achieve.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! ResultCollectionViewCell
cell.BtnView.tag = indexPath.item
cell.photoTitle.text = photoArrayFromSearchView[indexPath.row].title
cell.photoImg.af_setImage(withURL: photoArrayFromSearchView[indexPath.row].photoURL)
return cell }
It works but here is the problem.
I cant use the same way by button.tag to delete item.
When the item has been delete, somehow my button.tag won't reload.
And it crash. Error: Index out of range.
Because button.tag didn't reload but collectionView index is already change.
#IBAction func deleteBtn(_ sender: UIButton) {
let Index = IndexPath(row: sender.tag, section: 0)
deleteItem(btnTag: sender.tag)
fetch()
favoritePhotoCollection.deleteItems(at: [Index])
}
func deleteItem(btnTag:Int){
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
context.delete(favoriteArray[btnTag])
do {
try context.save()
} catch {
print("delete fail")
}
}
func fetch(){
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequset = NSFetchRequest<FavoritePhotos>(entityName: "FavoritePhotos")
fetchRequset.returnsObjectsAsFaults = false
do {
favoriteArray = try context.fetch(fetchRequset)
} catch {
print("fail to fetch")
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return favoriteArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as!
FavoriteCollectionViewCell
cell.BtnView.tag = indexPath.item
cell.photoTitle.text = favoriteArray[indexPath.row].photoTitle
if let data = favoriteArray[indexPath.row].photoImg as Data? {
cell.photoImg.image = UIImage(data: data)
}else {
cell.photoImg.image = nil
}
return cell
}
Can someone tell me how to fix this problem?
Or the better way to achieve using button inside CollcetionViewCell to delete or save data.
thx your patience to read.
It will not work because as collection view recycle. So that tag wouldn't be the same. What you can do is assigning something which is unique like you can use save title as a tag and try to save and extract using that tag.
The problem is the fetch line. Remove the item from CoreData and from the data source array but don't refetch the data.
#IBAction func deleteBtn(_ sender: UIButton) {
let indexPath = IndexPath(row: sender.tag, section: 0)
photoArrayFromSearchView.remove(at: indexPath.row)
deleteItem(btnTag: sender.tag)
favoritePhotoCollection.deleteItems(at: [indexPath])
}
There is another inconsistency: There are two different arrays favoriteArray and photoArrayFromSearchView
Instead of the button.tag, you could make a delete action delegate protocol(that has deleteCell func). Your view controller should conform the protocol and assign self to the cells. Then view controller can figure out the item's indexPath by "collectionView.indexPath(for :UICollectionViewCell)" and then delete the cell in the implementation of deleteCell func.

UICollectionViewCell reuse causing incorrect UISwitch state

I am having trouble finding a solution for this issue.
I am using UISwitch inside UICollectionViewCell and I am passing a boolean variable to set switch on or off.
The condition is only one switch has to be ON at a time from all cells.
But When I turn one switch on another random switch's tint color changes that means its state changed.
By default switch status is ON in storyboard and even if I set it OFF nothing changes.
I couldn't figure out why is this happening.
Here is my code for cellForItemAtIndexPath
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AddEditItemPopupView.cellId, for: indexPath) as! DiscountCollectionViewCell
cell.delegate = self
let currentDiscount = allDiscounts[indexPath.item]
let shouldApplyDiscount = updatedDiscountId == currentDiscount.id
cell.updateCellWith(data: currentDiscount, applyDiscount: shouldApplyDiscount)
return cell
}
And code for cell class
func updateCellWith(data: DiscountModel, applyDiscount: Bool) {
let name = data.title.replacingOccurrences(of: "Discount ", with: "")
self.titleLabel.text = String(format: "%# (%.2f%%)", name, data.value)
self.switchApply.isOn = applyDiscount
self.switchApply.tag = data.id
}
Data source contains objects of DiscountModel which look like this:
{
id: Int!
title: String!
value: Double!
}
Switch value changed method inside cell class:
#IBAction func switchValueChanged(_ sender: UISwitch) {
if sender.isOn {
self.delegate?.switchValueDidChangeAt(index: sender.tag)
}
else{
self.delegate?.switchValueDidChangeAt(index: 0)
}
}
Delegate method inside view controller class:
func switchValueDidChangeAt(index: Int) {
self.updatedDiscountId = index
self.discountCollectionView.reloadData()
}
There are a few improvements I would suggest to your code;
Reloading the entire collection view is a bit of a shotgun
Since it is possible for there to be no discount applied, you should probably use an optional for your selected discount, rather than "0"
Using Tag is often problematic
I would use something like:
var currentDiscount: DiscountModel? = nil
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AddEditItemPopupView.cellId, for: indexPath) as! DiscountCollectionViewCell
cell.delegate = self
let item = allDiscounts[indexPath.item]
self.configure(cell, forItem: item)
return cell
}
func configure(_ cell: DiscountCollectionViewCell, forItem item: DiscountModel) {
cell.switchApply.isOn = false
let name = item.title.replacingOccurrences(of: "Discount ", with: "")
self.titleLabel.text = String(format: "%# (%.2f%%)", name, item.value)
guard let selectedDiscount = self.currentDiscount else {
return
}
cell.switchApply.isOn = selectedDiscount.id == item.id
}
func switchValueDidChangeIn(cell: DiscountCollectionViewCell, to value: Bool) {
if value {
if let indexPath = collectionView.indexPath(for: cell) {
self.currentDiscount = self.allDiscounts[indexPath.item]
}
} else {
self.currentDiscount = nil
}
for indexPath in collectionView.indexPathsForVisibleItems {
if let cell = collectionView.cellForItem(at: indexPath) {
self.configure(cell, forItem: self.allDiscounts[indexPath.item])
}
}
}
In your cell:
#IBAction func switchValueChanged(_ sender: UISwitch) {
self.delegate?.switchValueDidChangeIn(cell:self, to: sender.isOn)
}

Get index of clicked UICollectionViewCell in UICollectionView Swift

How do I get the index of the "Sheep" I clicked on in a CollectionView made in Xcode with Swift for iOS?
class SheepsOverviewVC:
UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "class", for: indexPath) as! ClassesCollectionCell
if(sheeps.count > 0) {
cell.ClassImageView.image = UIImage(named: sheeps[indexPath.row] as! String)
cell.SheepName.text = names[indexPath.row] as? String
}
return cell
}
I created a Sent Event on the TouchDown via the Gui:
#IBAction func clickingSheep(_ sender: UIButton) {
print("This will show info about the Sheep")
print(sender)
}
But the response I get is from the second print:
<UIButton: 0x7f9a63021d20; frame = (50 50; 136 169); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x60800003d260>>
Probably there is some way to figure out which Sheep was clicked, but how do I get that information?
This is how it looks like (other namings then provided in the post):
One solution is to get the index path of the cell based on the button's location.
#IBAction func clickingSheep(_ sender: UIButton) {
let hitPoint = sender.convert(CGPoint.zero, to: collectionView)
if let indexPath = collectionView.indexPathForItem(at: hitPoint) {
// use indexPath to get needed data
}
}
You can set and check the button property "tag" (if you have the outlet set to the controller)
Here is another easy solution:
Have a new property for the callback.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "class", for: indexPath) as! ClassesCollectionCell
if(sheeps.count > 0) {
cell.ClassImageView.image = UIImage(named: sheeps[indexPath.row] as! String)
cell.SheepName.text = names[indexPath.row] as? String
}
cell.callBack = { [weak self] collectionViewCell in
let indexPath = collectionView.indexPath(for: collectionViewCell)
self?.doStuffFor(indexPath)
}
return cell
}
and on the cell you can have the ibaction
cell class
//...
var callBack : ((UICollectionViewCell?)->Void)?
//...
#IBAction func action(_ sender: UIButton) {
self.callBack?(self)
}

Reload Collection View in a Collection View Cell through delegation

I have a controller (A) with a Collection View that features 2 cell classes. One of them (B) contains another Collection View. After doing some research, I still cannot figure out how to update the cells in (B) from (A) or elsewhere to get what I want.
Issues
(B) does not reload properly when its button is pressed: the cell with whom the button was tied is still visible even though it is deleted from the userFriendRequests array in (A) in its delegate method. As a bonus I get a crash when I scroll to a new cell in (B) stating that "index is out of range" on the line cell.user = userFriendRequests[indexPath.row].
What I Have
Controller (A)
protocol UserFriendRequestsDelegate: class {
func didPressConfirmFriendButton(_ friendId: String?)
}
/...
fileprivate var userFriendRequests = [User]()
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if userFriendRequests.isEmpty == false {
switch indexPath.section {
case 0:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: friendRequestCellId, for: indexPath) as! UserFriendRequests
cell.userFriendRequests = userFriendRequests
cell.delegate = self
return cell
case 1:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! UserFriendCell
let user = users[indexPath.row]
cell.user = user
return cell
default:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! UserFriendCell
return cell
}
}
/...
extension AddFriendsController: UserFriendRequestsDelegate {
internal func didPressConfirmFriendButton(_ friendId: String?) {
guard let uid = FIRAuth.auth()?.currentUser?.uid, let friendId = friendId else {
return
}
let userRef = FIRDatabase.database().reference().child("users_friends").child(uid).child(friendId)
let friendRef = FIRDatabase.database().reference().child("users_friends").child(friendId).child(uid)
let value = ["status": "friend"]
userRef.updateChildValues(value) { (error, ref) in
if error != nil {
return
}
friendRef.updateChildValues(value, withCompletionBlock: { (error, ref) in
if error != nil {
return
}
self.setUpRequestsStatusesToConfirmed(uid, friendId: friendId)
DispatchQueue.main.async(execute: {
let index = self.currentUserFriendRequests.index(of: friendId)
self.currentUserFriendRequests.remove(at: index!)
for user in self.userFriendRequests {
if user.id == friendId {
self.userFriendRequests.remove(at: self.userFriendRequests.index(of: user)!)
}
}
self.attemptReloadOfCollectionView()
})
})
}
}
PS: self.attemptReloadOfCollectionView() is a func that simply invalidates a timer, sets it to 0.1 sec and then calls reloadData() on (A)'s Collection View.
CollectionViewCell (B)
weak var delegate: UserFriendRequestsDelegate?
var userFriendRequests = [User]()
/...
#objc fileprivate func confirmFriendButtonPressed(_ sender: UIButton) {
delegate?.didPressConfirmFriendButton(friendId)
}
/...
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return userFriendRequests.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: friendRequestCellId, for: indexPath) as! FriendRequestCell
cell.user = userFriendRequests[indexPath.row]
return cell
}
/...
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let firstName = userFriendRequests[indexPath.row].first_name, let lastName = userFriendRequests[indexPath.row].last_name, let id = userFriendRequests[indexPath.row].id else {
return
}
nameLabel.text = firstName + " " + lastName
friendId = id
confirmButton.addTarget(self, action: #selector(confirmFriendButtonPressed(_:)), for: .touchUpInside)
}
What I want to achieve
Update (B) when a User is removed from the userFriendRequests array in (A), this User being identified by his id passed by (B) through delegation.
Any good soul that might have an idea on how to tackle this issue ?
Thanks in advance for your help !

Resources