How to use the button inside CollcetionViewCell to delete cell itself - ios

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.

Related

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)
}

How to remove a firebase child node from a specific UICollectionViewCell - Swift

I have a UICollectionView which looks like this image and have the following data structure in Firebase.
I would like for the user to be able to delete individual posts from the collection view and subsequently from firebase. I have seen on other stackoverflow posts that say I must use the .removeValue from firebase, but don't know how to get the reference to the random child in order to delete it.
How can I access the autoId value from each post e.g "LPmNrvzu-aXsw_u-rEF " so I can remove that child node from Firebase?
Here's the path I was using to load all the user's posts from Firebase:
#objc func observeUserPosts() {
let uid = Auth.auth().currentUser?.uid
let postsRef = Database.database().reference().child("posts").queryOrdered(byChild: "author/userid")
postsRef.queryEqual(toValue: uid!).observe(.value) { (snapshot) in
}
}
This is the extension where I'm loading all the UICollectionView code
//extension - UICollectionView for user's posts
extension ProfileViewController: UICollectionViewDataSource,UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return postsuser.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: PostsCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "postsCell", for: indexPath) as! PostsCollectionViewCell
cell.set(post: postsuser[indexPath.row])
cell.deletePostButton.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
cell.deletePostButton.tag = indexPath.row
return cell
}
#objc func buttonAction(sender: UIButton) {
Database.database().reference().child("posts").queryOrdered(byChild: "author/userid").observe(.value) { (snapshot) in
if let posts = snapshot.value as? [String: AnyObject] {
for (key, _) in posts {
// NOW HOW DO I REFERENCE THE CELL THAT THE USER CLICKS TO DELETE?
}
}
}
// postsuser[sender.tag]
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let vc = storyboard?.instantiateViewController(withIdentifier: "profileUsersSelectedPostViewController") as? ProfileUsersSelectedPostViewController
self.navigationController?.pushViewController(vc!, animated: true)
vc?.selectedpostsuser = postsuser[indexPath.row]
}
}
This is how I managed to solve the question I had asked.... hope it helps :)
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: PostsCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "postsCell", for: indexPath) as! PostsCollectionViewCell
cell.set(post: postsuser[indexPath.row])
cell.deletePostButton.addTarget(self, action: #selector(buttonAction(sender:)), for: .touchUpInside)
cell.deletePostButton.tag = indexPath.row
return cell
}
#objc func buttonAction(sender: UIButton) {
ProgressHUD.show("Un momento", interaction: true)
let uid = Auth.auth().currentUser?.uid
Database.database().reference().child("posts").queryOrdered(byChild: "author/userid").queryEqual(toValue: uid!).observe(.value) { (snapshot) in
if let posts = snapshot.value as? [String: AnyObject] {
if let posts = snapshot.value as? [String: AnyObject] {
for (key, postReference) in posts {
if let post = postReference as? [String: Any], let timestamp = post["timestamp"] as? TimeInterval, timestamp == self.postsuser[sender.tag].timestampDouble {
Database.database().reference().child("posts").child(key).removeValue(completionBlock: { (error, _) in
DispatchQueue.main.async {
ProgressHUD.showSuccess("Tu imagen ha sido borrada...")
self.postsuser.remove(at: sender.tag)
self.postsCollectionView.reloadData()
self.refresher.endRefreshing()
}
})
}
}
}
}
}
}

Accessing indexPath in a collectionview

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]
}

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