I am Trying to add a Tap Gesture Recognizer to the header of my UICollection view, but no matter what, I can't get the numberOfPostsViewTapped() function to fire off. I've been trying for hours, and have tried using other UI elements such as other views or labels in the header view, but nothing is helping. Some guidance would be much appreciated.
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader: // only checking header - no footer on this view
// use an external class for the header UICollectionViewCell in order to set outlets on a non-reusable cell
// if you try to set outlets on a reusable cell, such as a header, it will fail
let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "Header", forIndexPath: indexPath) as! ProfileCollectionViewHeader
// dynamically set user profile information
headerView.usernameTextLabel.text = user?.name
headerView.numberOfPostsTextLabel.text = user?.numberOfPosts != nil ? "\(user!.numberOfPosts!)" : "0"
let numberOfPostsViewSelector : Selector = #selector(self.numberOfPostsViewTapped)
let viewPostsViewGesture = UITapGestureRecognizer(target: self, action: numberOfPostsViewSelector)
viewPostsViewGesture.numberOfTapsRequired = 1
viewPostsViewGesture.delaysTouchesBegan = true
headerView.numberOfPostsTextLabel.userInteractionEnabled = true;
headerView.numberOfPostsTextLabel.addGestureRecognizer(viewPostsViewGesture)
return headerView
default:
assert(false, "Unexpected element kind")
}
}
func numberOfPostsViewTapped(sender: UITapGestureRecognizer){
print("HErE")
}
Check your frames. Is your UICollectionView within its frame?
If a view is out of its frame, and doesn't clip to its bounds, it will show the view's contents, though user interaction will not work with it.
I think you need to set userInteractionEnabled to true to your headerView so that the tapping would reach your label which is a child of your headerView.
headerView.userInteractionEnabled = true
Made few changes to your code and this worked for me
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionView.elementKindSectionHeader:
guard
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "AddLinksReusableView", for: indexPath) as? AddLinksReusableView else {
fatalError("Invalid view type")
}
let numberOfPostsViewSelector : Selector = #selector(self.imgVuTapped)
let viewPostsViewGesture = UITapGestureRecognizer(target: self, action: numberOfPostsViewSelector)
headerView.profileImg.isUserInteractionEnabled = true
viewPostsViewGesture.numberOfTapsRequired = 1
viewPostsViewGesture.delaysTouchesBegan = true
headerView.profileImg.addGestureRecognizer(viewPostsViewGesture)
return headerView
default:
assert(false, "Invalid element type")
}
}
#objc func imgVuTapped (sender: UITapGestureRecognizer){
print("HErE, Its working")
}
Happy Codding
Related
I implement a simple drag and drop sample.
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private var collectionView: UICollectionView?
var colors: [UIColor] = [
.link,
.systemGreen,
.systemBlue,
.red,
.systemOrange,
.black,
.systemPurple,
.systemYellow,
.systemPink,
.link,
.systemGreen,
.systemBlue,
.red,
.systemOrange,
.black,
.systemPurple,
.systemYellow,
.systemPink
]
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: view.frame.size.width/3.2, height: view.frame.size.width/3.2)
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
//collectionView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
let customCollectionViewCellNib = CustomCollectionViewCell.getUINib()
collectionView?.register(customCollectionViewCellNib, forCellWithReuseIdentifier: "cell")
collectionView?.delegate = self
collectionView?.dataSource = self
collectionView?.backgroundColor = .white
view.addSubview(collectionView!)
let gesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture))
collectionView?.addGestureRecognizer(gesture)
}
#objc func handleLongPressGesture(_ gesture: UILongPressGestureRecognizer) {
guard let collectionView = collectionView else {
return
}
switch gesture.state {
case .began:
guard let targetIndexPath = collectionView.indexPathForItem(at: gesture.location(in: self.collectionView)) else {
return
}
collectionView.beginInteractiveMovementForItem(at: targetIndexPath)
case .changed:
collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: collectionView))
case .ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView?.frame = view.bounds
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return colors.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = colors[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.size.width/3.2, height: view.frame.size.width/3.2)
}
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let item = colors.remove(at: sourceIndexPath.row)
colors.insert(item, at: destinationIndexPath.row)
}
}
However, I notice that, if my UICollectionViewCell is created with XIB, it will randomly exhibit flickering behaviour, during drag and drop.
The CustomCollectionViewCell is a pretty straightforward code.
CustomCollectionViewCell.swift
import UIKit
extension UIView {
static func instanceFromNib() -> Self {
return getUINib().instantiate(withOwner: self, options: nil)[0] as! Self
}
static func getUINib() -> UINib {
return UINib(nibName: String(describing: self), bundle: nil)
}
}
class CustomCollectionViewCell: UICollectionViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
Flickering
By using the following code
let customCollectionViewCellNib = CustomCollectionViewCell.getUINib()
collectionView?.register(customCollectionViewCellNib, forCellWithReuseIdentifier: "cell")
It will have the following random flickering behaviour - https://youtu.be/CbcUAHlRJKI
No flickering
However, if the following code is used instead
collectionView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
Things work fine. There are no flickering behaviour - https://youtu.be/QkV2HlIrXK8
May I know why it is so? How can I avoid the flickering behaviour, when my custom UICollectionView is created from XIB?
Please note that, the flickering behaviour doesn't happen all the time. It happens randomly. It is easier to reproduce the problem using real iPhone device, than simulator.
Here's the complete sample code - https://github.com/yccheok/xib-view-cell-cause-flickering
While we are rearranging cells in UICollectionView (gesture is active), it handles all of the cell movements for us (without having us to worry about changing dataSource while the rearrange is in flight).
At the end of this rearrange gesture, UICollectionView rightfully expects that we will reflect the change in our dataSource as well which you are doing correctly here.
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let item = colors.remove(at: sourceIndexPath.row)
colors.insert(item, at: destinationIndexPath.row)
}
Since UICollectionView expects a dataSource update from our side, it performs following steps -
Call our collectionView(_:, moveItemAt:, to:) implementation to provide us a chance to reflect the changes in dataSource.
Call our collectionView(_:, cellForItemAt:) implementation for the destinationIndexPath value from call #1, to re-create a new cell at that indexPath from scratch.
Okay, but why would it perform step 2 even if this is the correct cell to be at that indexPath?
It's because UICollectionView doesn't know for sure whether you actually made those dataSource changes or not. What happens if you don't make those changes? - now your dataSource & UI are out of sync.
In order to make sure that your dataSource changes are correctly reflected in the UI, it has to do this step.
Now when the cell is being re-created, you sometimes see the flicker. Let the UI reload the first time, put a breakpoint in the cellForItemAt: implementation at the first line and rearrange a cell. Right after rearrange completes, your program will pause at that breakpoint and you can see following on the screen.
Why does it not happen with UICollectionViewCell class (not XIB)?
It does (as noted by others) - it's less frequent. Using the above steps by putting a breakpoint, you can catch it in that state.
How to solve this?
Get a reference to the cell that's currently being dragged.
Return this instance from cellForItemAt: implementation.
var currentlyBeingDraggedCell: UICollectionViewCell?
var willRecreateCellAtDraggedIndexPath: Bool = false
#objc func handleLongPressGesture(_ gesture: UILongPressGestureRecognizer) {
guard let cv = collectionView else { return }
let location = gesture.location(in: cv)
switch gesture.state {
case .began:
guard let targetIndexPath = cv.indexPathForItem(at: location) else { return }
currentlyBeingDraggedCell = cv.cellForItem(at: targetIndexPath)
cv.beginInteractiveMovementForItem(at: targetIndexPath)
case .changed:
cv.updateInteractiveMovementTargetPosition(location)
case .ended:
willRecreateCellAtDraggedIndexPath = true
cv.endInteractiveMovement()
default:
cv.cancelInteractiveMovement()
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if willRecreateCellAtDraggedIndexPath,
let currentlyBeingDraggedCell = currentlyBeingDraggedCell {
self.willRecreateCellAtDraggedIndexPath = false
self.currentlyBeingDraggedCell = nil
return currentlyBeingDraggedCell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.contentView.backgroundColor = colors[indexPath.item]
return cell
}
Will this solve the problem 100%?
NO. UICollectionView will still remove the cell from it's view hierarchy and ask us for a new cell - we are just providing it with an existing cell instance (that we know is going to be correct according to our own implementation).
You can still catch it in the state where it disappears from UI before appearing again. However this time there's almost no work to be done, so it will be significantly faster and you will see the flickering less often.
BONUS
iOS 15 seems to be working on similar problems via UICollectionView.reconfigureItems APIs. See an explanation in following Twitter thread.
Whether these improvements will land in rearrange or not, we will have to see.
Other Observations
Your UICollectionViewCell subclass' XIB looks like following
However it should look like following (1st one is missing contentView wrapper, you get this by default when you drag a Collection View Cell to the XIB from the View library OR create a UICollectionViewCell subclass with XIB).
And your implementation uses -
cell.backgroundColor = colors[indexPath.row]
You should use contentView to do all the UI customization, also note the indexPath.item(vs row) that better fits with cellForItemAt: terminology (There are no differences in these values though). cellForRowAt: & indexPath.row are more suited for UITableView instances.
cell.contentView.backgroundColor = colors[indexPath.item]
UPDATE
Should I use this workaround for my app in production?
NO.
As noted by OP in the comments below -
The proposed workaround has 2 shortcomings.
(1) Missing cell
(2) Wrong content cell.
This is clearly visible in https://www.youtube.com/watch?v=uDRgo0Jczuw Even if you perform explicit currentlyBeingDraggedCell.backgroundColor = colors[indexPath.item] within if block, wrong content cell issue is still there.
The flickering is caused by the cell being recreated at its new position. You can try holding to the cell.
(only the relevant code is shown)
// keeps a reference to the cell being dragged
private weak var draggedCell: UICollectionViewCell?
// the flag is set when the dragging completes
private var didInteractiveMovementEnd = false
#objc func handleLongPressGesture(_ gesture: UILongPressGestureRecognizer) {
switch gesture.state {
case .began:
// keep cell reference
draggedCell = collectionView.cellForItem(at: targetIndexPath)
collectionView.beginInteractiveMovementForItem(at: targetIndexPath)
case .ended:
// reuse the cell in `cellForItem`
didInteractiveMovementEnd = true
collectionView.performBatchUpdates {
collectionView.endInteractiveMovement()
} completion: { completed in
self.draggedCell = nil
self.didInteractiveMovementEnd = false
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// reuse the dragged cell
if didInteractiveMovementEnd, let draggedCell = draggedCell {
return draggedCell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
...
}
I'm making a music app recently, and I'd like to know how to pass data from CollectionView to TableView which has several sections. Here is the home page of my project, and what I want to do is when user tap the image, it will precent another ViewController with the information about the picture. I already know how to present a ViewController by clicking CollectionViewCell inside TableView by delegate, but only if there's only one section.
The thing is that I have 5 sections in this page, and I also have 5 different models for encoding the JSON from API. So how I show the pictures is to send the image urls(from 5 models) to TableViewCell in each section like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: HomeTableViewCell.identifier, for: indexPath) as? HomeTableViewCell else { return UITableViewCell() }
cell.delegate = self
switch homeSections[indexPath.section] {
case .newReleases:
if let newReleases = self.newReleases?.albums.items.map({$0.images[0].url}) {
cell.getPictures = newReleases
}
return cell
case .followSingers:
if let currentlyFollowing = self.currentlyFollowing?.artists.items.map({$0.images[0].url}) {
cell.getPictures = currentlyFollowing
cell.isCircle = true
}
return cell
case .catrgories:
if let categories = self.musicCategory?.playlists.items.map({$0.images[0].url}) {
cell.getPictures = categories
}
return cell
case .artists:
if let playlist = self.relatedArtists?.artists.map({$0.images[0].url}){
cell.getPictures = playlist
}
return cell
case .recentlyPlayed:
if let recentlyPlayed = self.recentlyPlayed?.items?.map({$0.track.album.images[0].url}) {
cell.getPictures = recentlyPlayed
}
return cell
}
}
However, when I want to pass the information which the user tap, there's nothing I can pass but indexPath. I've tried to declare the 5 different model types in TableViewCell, but I still don't know which section did the user tap. Does anyone can help? Thanks a lot!
Update:
To make the question more clearly, please refer to the information below.
In this page, I have a TableView and embed a CollectionView in the TableViewCell, and there's only one ImageView in the CollectionViewCell.
The "New Releases", "Currently Following" and "Categories" are the header of TableView. The "New Releases" is the first section, and the "Currently Following" is the second section, and so on.
Here is how I set cellForItem in CollectionView Delegate. It basically converts String to URL, and display the picture on the screen.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeCollectionViewCell.identifier, for: indexPath) as? HomeCollectionViewCell else { return UICollectionViewCell() }
guard let url = URL(string: getPictures[indexPath.row]) else { return UICollectionViewCell() }
cell.myImageView.getImages(url: url)
if isCircle == true {
cell.myImageView.layer.cornerRadius = cell.myImageView.frame.width/2
}
return cell
}
When the user taps the image, it will only trigger the didSelectItemAt in CollectionView. And I can only pass indexPath.row so far.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
delegate?.sendIndexPath(index: indexPath.row)
}
I'm trying to allow user interaction in my collection view. I have decided to try to implement UITapGestureRecognizer to do this. I have tried adding a UITapGestureRecognizer to the collectionview itself and to the collectionview cell. Both ways crash the app. Here is how I am adding the UITapGestureRecognizer to the cell.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionview.dequeueReusableCell(withReuseIdentifier: "userCell", for: indexPath) as! UserCell
cell.userImage.sd_setImage(with: URL(string: self.user[indexPath.row].imagePath))
cell.nameLabel.text = self.user[indexPath.row].username
cell.userID = self.user[indexPath.row].userID
let singleTap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "segueToProfile:")
singleTap.numberOfTapsRequired = 1
singleTap.numberOfTouchesRequired = 1
cell.addGestureRecognizer(singleTap)
return cell
}
When I tap on the cell I get a SIGABRT in the AppDelegate. The error message reads "terminating with uncaught exception of type NSException". What am I doing wrong. UITapGestureRecognizer.
This is my segueToProfile function:
func segueToProfile(gesture: UITapGestureRecognizer) {
// if(recognizer.state == UIGestureRecognizer.State.ended){
// print("myUIImageView has been tapped by the user.")
// }
print("hell world")
}
If you use didSelectItemAt method of collectionView your codebase look readable and maintainable.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
currentViewController.performSegue(withIdentifier: "YourSegueName", sender: nil)
}
First, I would get rid of tapgesturerecognizer as didSelectItem should handle what you are trying to accomplish. That being said, in order for this to work you must:
Remove tapgesturerecognizer
Ensure that the collection view delegate is set to self.
e.g. <yourColletionViewName>.delegate = self
Above can be assigned at viewDidLoad()
I am having difficulty getting UICollectionView Content Insets Adjustment Behavior setting to work. It appears to be modifying the location of 'ghost' ReusableView instead of my header view. In this screenshot the ghost view is selected, the header view I'm trying to control is green (its bg color).
I have tried using both the story board and programatic options, both are moving the position of the ghost view instead of my visible view. I cannot find where these ghost views are being generated nor can I see them in storyboard.
screenshot of ghost views
Here is my reusable view function:
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if(kind == "UICollectionElementKindSectionHeader"){
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "questionHeader", for: indexPath) as! QuestionheaderCollectionReusableView
if(self.mode == .Test){
view.scoreRing.alpha = 0.5
view.scoreRing.style = .ontop
}else{
view.updateScore(session: QKSession.default)
}
if((activeQuestion) != nil){
view.questionLabel.text = activeQuestion?.question
view.updateProgress(session: QKSession.default, question: activeQuestion!)
}else{
view.questionLabel.text = "Please select a MQF"
}
return view
}else{
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "questionFooter", for: indexPath) as! QuestionFooterCollectionReusableView
return view
}
}
Has anyone else seen anything like this?
Here's a problem which I have been stuck at for quite some time now.
Here's the code
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)
collectionViewLove?.performBatchUpdates({() -> Void in
self.collectionViewLove?.deleteItemsAtIndexPaths([indexPath])
self.wishlist?.results.removeAtIndex(indexPath.row)
self.collectionViewLove?.reloadData()}, completion: nil)}
I have a button inside each UICollectionViewCell which deletes it on clicking. The only way for me to retrieve the indexPath is through the button tag. I have initialized the button tag in
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
However every time I delete, the first time it deletes the corresponding cell whereas the next time it deletes the cell follwing the one I clicked. The reason is that my button tag is not getting updated when I call the function reloadData().
Ideally, when I call the reloadData() ,
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
should get called and update the button tag for each cell. But that is not happening. Solution anyone?
EDIT:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
collectionView.registerNib(UINib(nibName: "LoveListCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! LoveListCollectionViewCell
cell.imgView.hnk_setImageFromURL(NSURL(string: (wishlist?.results[indexPath.row].image)!)!, placeholder: UIImage(named: "preloader"))
let item = self.wishlist?.results[indexPath.row]
cell.layer.borderColor = UIColor.grayColor().CGColor
cell.layer.borderWidth = 1
cell.itemName.text = item?.title
cell.itemName.numberOfLines = 1
if(item?.price != nil){
cell.price.text = "\u{20B9} " + (item?.price.stringByReplacingOccurrencesOfString("Rs.", withString: ""))!
}
cell.price.adjustsFontSizeToFitWidth = true
cell.deleteButton.tag = indexPath.row
cell.deleteButton.addTarget(self, action: "removeFromLoveList:", forControlEvents: .TouchUpInside)
cell.buyButton.tag = indexPath.row
cell.buyButton.backgroundColor = UIColor.blackColor()
cell.buyButton.addTarget(self, action: "buyAction:", forControlEvents: .TouchUpInside)
return cell
}
A couple of things:
You're doing too much work in cellForItemAtIndexPath--you really want that to be as speedy as possible. For example, you only need to register the nib once for the collectionView--viewDidLoad() is a good place for that. Also, you should set initial state of the cell in the cell's prepareForReuse() method, and then only use cellForItemAtIndexPath to update with the custom state from the item.
You shouldn't reload the data until the deletion is complete. Move reloadData into your completion block so the delete method is complete and the view has had time to update its indexes.
However, it would be better if you didn't have to call reloadData in the first place. Your implementation ties the button's tag to an indexPath, but these mutate at different times. What about tying the button's tag to, say, the wishlist item ID. Then you can look up the appropriate indexPath based on the ID.
Revised code would look something like this (untested and not syntax-checked):
// In LoveListCollectionViewCell
override func prepareForReuse() {
// You could also set these in the cell's initializer if they're not going to change
cell.layer.borderColor = UIColor.grayColor().CGColor
cell.layer.borderWidth = 1
cell.itemName.numberOfLines = 1
cell.price.adjustsFontSizeToFitWidth = true
cell.buyButton.backgroundColor = UIColor.blackColor()
}
// In your UICollectionView class
// Cache placeholder image since it doesn't change
private let placeholderImage = UIImage(named: "preloader")
override func viewDidLoad() {
super.viewDidLoad()
collectionView.registerNib(UINib(nibName: "LoveListCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! LoveListCollectionViewCell
cell.imgView.hnk_setImageFromURL(NSURL(string: (wishlist?.results[indexPath.row].image)!)!, placeholder: placeholderImage)
let item = self.wishlist?.results[indexPath.row]
cell.itemName.text = item?.title
if(item?.price != nil){
cell.price.text = "\u{20B9} " + (item?.price.stringByReplacingOccurrencesOfString("Rs.", withString: ""))!
}
cell.deleteButton.tag = item?.id
cell.deleteButton.addTarget(self, action: "removeFromLoveList:", forControlEvents: .TouchUpInside)
cell.buyButton.tag = item?.id
cell.buyButton.addTarget(self, action: "buyAction:", forControlEvents: .TouchUpInside)
return cell
}
func removeFromLoveList(sender: AnyObject?) {
let id = sender.tag
let index = wishlist?.results.indexOf { $0.id == id }
let indexPath = NSIndexPath(forRow: index, inSection: 0)
collectionViewLove?.deleteItemsAtIndexPaths([indexPath])
wishlist?.results.removeAtIndex(index)
}
It's probably not a good idea to be storing data in the cell unless it is needed to display the cell. Instead your could rely on the UICollectionView to give you the correct indexPath then use that for the deleting from your data source and updating the collectionview.
To do this use a delegate pattern with cells.
1.Define a protocol that your controller/datasource should conform to.
protocol DeleteButtonProtocol {
func deleteButtonTappedFromCell(cell: UICollectionViewCell) -> Void
}
2.Add a delegate property to your custom cell which would call back to the controller on the delete action. The important thing is to pass the cell in to that call as self.
class CustomCell: UICollectionViewCell {
var deleteButtonDelegate: DeleteButtonProtocol!
// Other cell configuration
func buttonTapped(sender: UIButton){
self.deleteButtonDelegate.deleteButtonTappedFromCell(self)
}
}
3.Then back in the controller implement the protocol function to handle the delete action. Here you could get the indexPath for the item from the collectionView which could be used to delete the data and remove the cell from the collectionView.
class CollectionViewController: UICollectionViewController, DeleteButtonProtocol {
// Other CollectionView Stuff
func deleteButtonTappedFromCell(cell: UICollectionViewCell) {
let deleteIndexPath = self.collectionView!.indexPathForCell(cell)!
self.wishList.removeAtIndex(deleteIndexPath.row)
self.collectionView?.performBatchUpdates({ () -> Void in
self.collectionView?.deleteItemsAtIndexPaths([deleteIndexPath])
}, completion: nil)
}
}
4.Make sure you set the delegate for the cell when configuring it so the delegate calls back to somewhere.
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
//Other cell configuring here
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("identifier", forIndexPath: indexPath)
(cell as! CustomCell).deleteButtonDelegate = self
return cell
}
}
I was facing the similar issue and I found the answer by just reloading collection view in the completion block.
Just update your code like.
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)
collectionViewLove?.performBatchUpdates({
self.collectionViewLove?.deleteItemsAtIndexPaths([indexPath])
self.wishlist?.results.removeAtIndex(indexPath.row)
}, completion: {
self.collectionViewLove?.reloadData()
})
which is mentioned in UICollectionView Performing Updates using performBatchUpdates by Nik