Swift 5 CollectionView get indexPath by longPress cell - swift

I am looking way to get indexPath or data when i do longPress on cell. Basically i can to delete album from collectionView, to do that i need to get id.
My cellForItem
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AlbumCollectionViewCell", for: indexPath) as! AlbumCollectionViewCell
cell.data = albumsDataOrigin[indexPath.row]
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGetstureDetected))
cell.addGestureRecognizer(longPressGesture)
return cell
}
longPressGetstureDetected
#objc func longPressGetstureDetected(){
self.delegateAlbumView?.longPressGetstureDetected()
}
delete func
func longPressGetstureDetected() {
showAlertWith(question: "You wanna to delete this album?", success: {
self.deleteAlbum() //Here i need to pass ID
}, failed: {
print("Delete cenceled")
})
}
For people who looking complete answer
#objc func longPress(_ longPressGestureRecognizer: UILongPressGestureRecognizer) {
if longPressGestureRecognizer.state == UIGestureRecognizer.State.began {
let touchPoint = longPressGestureRecognizer.location(in: collectionView)
if let index = collectionView.indexPathForItem(at: touchPoint) {
self.delegateAlbumView?.longPressGetstureDetected(id: albumsDataOrigin[index.row].id ?? 0)
}
}
}

Start by getting the coordinates of the press using gesture.location(in:) Ref: https://developer.apple.com/documentation/uikit/uigesturerecognizer/1624219-location
Then use indexPathForItem(at:) to retrieve the IndexPath of the cell touched. Ref: https://developer.apple.com/documentation/uikit/uicollectionview/1618030-indexpathforitem
Based on this you probably do not need a different gesture recognizer for each cell, you can probably register it with the collection view once.
Solution provided by George Heints based on the above:
#objc func longPress(_ longPressGestureRecognizer: UILongPressGestureRecognizer) {
if longPressGestureRecognizer.state == UIGestureRecognizer.State.began {
let touchPoint = longPressGestureRecognizer.location(in: collectionView)
if let index = collectionView.indexPathForItem(at: touchPoint) {
self.delegateAlbumView?.longPressGetstureDetected(id: albumsDataOrigin[index.row].id ?? 0)
}
}
}
I would recommend to use the State.recognized instead of State.began, your mileage may vary!

import UIKit
extension UIResponder {
func next<T: UIResponder>(_ type: T.Type) -> T? {
return next as? T ?? next?.next(type)
}
}
extension UICollectionViewCell {
var collectionView: UICollectionView? {
return next(UICollectionView.self)
}
var indexPath: IndexPath? {
return collectionView?.indexPath(for: self)
}
}
By the help with this extension you can know the indexPath of collection view from collection view cell file. And you can simply find the id of the photo by the indexPath from data array and delete it.

Related

Some UICollectionViewDropDelegateMethods get not called

I have an iPad application where you can drag images from Safari into a collection view. I also want to to drag and drop within the collection view to rearrange the collection View cells.
The drag and drop from safari is working. Dragging a cell within the collection view is also possible, but dropping is not possible. I took a look at the UICollectionViewDropDelegate methods "canLoadObjects", "sessionDidUpdate" and "performDrop" which get called in this order. I made another simple collection view app with drag and drop, so I know what to do.
I recognized that in the simple app, when I drag the cell onto another place "canLoadObjects" get called. I confirmed that with a breakpoint. In the other app this does not happen. Instead only "performDrop" gets called, but not "canLoadObjects" and "sessionDidUpdate". I set the delegate correctly (otherwise "performDrop would not get called either).
Does anybody know why some delegate methods doesn't get called? Thanks!
import UIKit
class ImageGalleryViewController: UIViewController {
var cellWidthScale: CGFloat = 1.0
var currentCellSize = CGSize()
var imageFetcher : ImageFetcher!
var loadAllImages = false
var tappedCellIndex = 0
#IBOutlet weak var collectionView: UICollectionView!{
didSet{
collectionView.delegate = self
collectionView.dataSource = self
collectionView.dragInteractionEnabled = true
collectionView.dragDelegate = self
collectionView.dropDelegate = self
collectionView.addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: #selector(adjustCellWidth(with:))))
}
}
var userIsPinching = false
var imageDataTuple : [(NSURL?, CGSize)]? {
didSet{
if loadAllImages == true{
collectionView?.reloadData()
loadAllImages = false
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UINib(nibName: "ImageCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "ImageCell")
}
#objc func adjustCellWidth(with recognizer: UIPinchGestureRecognizer){
switch recognizer.state {
case .changed:
userIsPinching = true
cellWidthScale *= recognizer.scale
recognizer.scale = 1.0
collectionView.collectionViewLayout.invalidateLayout()
default: break
}
}
}
//MARK: UICollectionViewDelegate
extension ImageGalleryViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageDataTuple?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath)
if let imageCell = cell as? ImageCollectionViewCell{
imageCell.imageView.image = nil
imageCell.spinner.startAnimating()
if let url = imageDataTuple?[indexPath.row].0{
imageCell.imageUrl = url
//never fetch data on the main queue
DispatchQueue.global(qos: .userInitiated).async {
let urlContent = try? Data(contentsOf: (url as URL).imageURL)
//update the UI always on the main queue
DispatchQueue.main.async {
//check if the url is really the one we want to show
if let imageData = urlContent, url == imageCell.imageUrl, let image = UIImage(data: imageData){
imageCell.spinner.stopAnimating()
imageCell.imageView.image = image
}
else{
imageCell.spinner.stopAnimating()
imageCell.imageView.image = UIImage(systemName: "xmark.octagon.fill")
imageCell.imageView.tintColor = UIColor.red
imageCell.imageView.sizeToFit()
}
}
}
}
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if let checkedImageData = imageDataTuple{
let imageHeightToWidth = checkedImageData[indexPath.row].1.height/checkedImageData[indexPath.row].1.width
let calculatedWidth = (self.view.frame.width/4) * cellWidthScale
currentCellSize = CGSize(width: calculatedWidth, height: calculatedWidth * imageHeightToWidth)
return currentCellSize
}
return CGSize.zero
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
tappedCellIndex = indexPath.item
performSegue(withIdentifier: "ShowImage", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowImage"{
if let destVC = segue.destination as? DetailImageViewController{
destVC.imageUrl = imageDataTuple![tappedCellIndex].0
}
}
}
}
//MARK: UICollectionViewDropDelegate
extension ImageGalleryViewController: UICollectionViewDropDelegate{
//1. Can the dropDelegate handle the object?
func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
//requirement: only drop with image and url should be handled
return session.canLoadObjects(ofClass: NSURL.self)
}
//2. update the session. Return a propsal in which manner the object schould be dropped (.copy; .move...)
func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
//Es wird überprüft, ob der collectionView auf dem gedroppt werden soll der ist, von dem das DragItem stammt
let isSelf = (session.localDragSession?.localContext as? UICollectionView) == collectionView
let dropOperation: UIDropOperation = isSelf ? .move : .copy
//intent: Soll eine neue Cell erstellt werden oder in die eingefügt werden, über der man sich gerde befindet?
return UICollectionViewDropProposal(operation: dropOperation, intent: .insertAtDestinationIndexPath)
}
//3. perform the actual drop. update UI and the model
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
let destinationIntexPath = coordinator.destinationIndexPath ?? IndexPath(row: 0, section: 0)
for item in coordinator.items{
//Is the drop from the same collectionView or external? internal --> != nil
if let sourceIndexPath = item.sourceIndexPath{
if let url = item.dragItem.localObject as? NSURL{
collectionView.performBatchUpdates {
imageDataTuple?.remove(at: sourceIndexPath.item)
imageDataTuple?.insert((url, currentCellSize), at: destinationIntexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [destinationIntexPath])
}
coordinator.drop(item.dragItem, toItemAt: destinationIntexPath)
}
}
//external --> nil
else{
var dataTupel: (NSURL?, CGSize?) {
didSet{
DispatchQueue.main.async {
if let controllerTitel = self.title, let presentImageSize = dataTupel.1, let presentURL = dataTupel.0{
if ImageGalleryModel.imageURLs[controllerTitel] != nil {
ImageGalleryModel.imageURLs[controllerTitel]!.append((presentURL, presentImageSize))
print(ImageGalleryModel.imageURLs)
self.imageDataTuple = ImageGalleryModel.imageURLs[controllerTitel]!
self.collectionView.insertItems(at: [IndexPath(item: ImageGalleryModel.imageURLs[controllerTitel]!.count - 1, section: 0)])
}
}
}
}
}
item.dragItem.itemProvider.loadObject(ofClass: NSURL.self) { provider, error in
dataTupel.0 = provider as? NSURL
}
item.dragItem.itemProvider.loadObject(ofClass: UIImage.self) { provider, error in
if let receivedImage = provider as? UIImage{
dataTupel.1 = receivedImage.size
}
}
}
}
}
}
//MARK: UICollectionViewDragDelegate
extension ImageGalleryViewController: UICollectionViewDragDelegate{
//Proveide inital drag items
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
return dragItem(at: indexPath)
}
//Provide drag items which were added while dragging
func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
session.localContext = collectionView
return dragItem(at: indexPath)
}
private func dragItem(at indexPath: IndexPath) -> [UIDragItem]{
if let image = (collectionView.cellForItem(at: indexPath) as? ImageCollectionViewCell)?.imageView.image{
let dragItem = UIDragItem(itemProvider: NSItemProvider(object: image))
dragItem.localObject = image
return [dragItem]
}
return []
}
}
//MARK: New selected Gallery
extension ImageGalleryViewController: GalleryListViewControllerDelegate{
func gallerySelected(name: String) {
loadAllImages = true
imageDataTuple = ImageGalleryModel.imageURLs[name]
self.title = name
}
}
The problem was that the auto correction chose delegate methods of the UIDropInteraction-delegate and not from the UICollectionViewDropDelegate. Just picked the correct methods and everything works as intended.

UICollectionViewCell delegate won't fire

I'm obviously doing something wrong but unable as of yet to determine where. I setup the cell as follows:
protocol PropertyPhotoCellDelegate: class {
func deletePropertyPhoto(cell: PropertyPhotoCell)
}
class PropertyPhotoCell: UICollectionViewCell {
weak var propertyPhotoCellDelegate: PropertyPhotoCellDelegate?
let deleteButton: UIButton = {
let button = UIButton()
let image = UIImage(named: "delete.png")
button.setImage(image, for: .normal)
button.showsTouchWhenHighlighted = true
button.isHidden = true
button.addTarget(self, action: #selector(handleDeleteButton), for: .touchUpInside)
return button
}()
var isEditing: Bool = false {
didSet {
deleteButton.isHidden = !isEditing
}
}
I've omitted setting up the cell views. Here is the selector
#objc fileprivate func handleDeleteButton() {
propertyPhotoCellDelegate?.deletePropertyPhoto(cell: self)
}
In the UICollectionViewController, I assign the delegate
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kCellId, for: indexPath) as! PropertyPhotoCell
cell.photoImageView.image = photos[indexPath.item]
cell.propertyPhotoCellDelegate = self
return cell
}
This hides or shows the delete button on the cell for all the cells in view
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
navigationItem.rightBarButtonItem?.isEnabled = !editing
if let indexPaths = collectionView?.indexPathsForVisibleItems {
for indexPath in indexPaths {
if let cell = collectionView?.cellForItem(at: indexPath) as? PropertyPhotoCell {
cell.deleteButton.isHidden = !isEditing
}
}
}
}
And finally, conforming to the protocol here
extension PropertyPhotosController: PropertyPhotoCellDelegate {
func deletePropertyPhoto(cell: PropertyPhotoCell) {
if let indexPath = collectionView?.indexPath(for: cell) {
photos.remove(at: indexPath.item)
collectionView?.deleteItems(at: [indexPath])
}
}
}
I tap the UICollectionViewController Edit button and all the cells show the delete button as expected. Any of the cell's delete button highlights on tap, but I don't see the delegate getting called.
When the delegate is assigned in the UICollectionViewController, also set the selector for the cell.
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kCellId, for: indexPath) as! PropertyPhotoCell
cell.photoImageView.image = photos[indexPath.item]
cell.propertyPhotoCellDelegate = self
cell.deleteButton.addTarget(cell, action: #selector(cell.handleDeleteButton), for: .touchUpInside)
cell.deleteButton.isHidden = true
return cell
}

Error when peeking and popping from collection view

Received an error
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
when trying to peek and pop from a collection view. I've checked my data struct and index path but everything seems to be fine.
Here's my code for the collections view
class thisSeaonViewController: UICollectionViewController, UIViewControllerPreviewingDelegate {
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
var URLArrayStringThisSeason = [String]()
var currentURL = String()
override func viewDidLoad() {
generateData()
if( traitCollection.forceTouchCapability == .available){
registerForPreviewing(with: self as! UIViewControllerPreviewingDelegate, sourceView: view)
}
}
override func viewDidAppear(_ animated: Bool) {
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
let imageView = cell.viewWithTag(1) as! UIImageView
let url = NSURL(string: URLArrayStringThisSeason[indexPath.row])
let placeholderImage = UIImage(named: "Rectangle")!
let filter = AspectScaledToFillSizeWithRoundedCornersFilter(
size: imageView.frame.size,
radius: 0
)
imageView.af_setImage(withURL: url as! URL, placeholderImage: placeholderImage, filter: filter, imageTransition: .crossDissolve(0.2)
)
cell.backgroundColor = UIColor.init(hexString: "#F3F3F3")
cell.layer.cornerRadius = 3.0
return cell
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return URLArrayStringThisSeason.count
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let vc = storyboard?.instantiateViewController(withIdentifier: "gridDetailedView") as! gridDetailedViewController
vc.imageURL = URLArrayStringThisSeason[indexPath.row]
self.navigationController?.pushViewController(vc, animated: true)
}
func generateData() {
if URLArrayStringThisSeason.count == 0 {
self.activityIndicator.isHidden = false
self.activityIndicator.startAnimating()
}
let queryThisSeason = FIRDatabase.database().reference().child("thisSeason")
queryThisSeason.keepSynced(true)
queryThisSeason.observeSingleEvent(of: .value, with: {(snapshot) in
if snapshot.childrenCount != 0 {
let urlArray = snapshot.value as! [String]
let urlLimitedArray = Array(urlArray.reversed())
self.URLArrayStringThisSeason = urlLimitedArray
self.collectionView?.reloadData()
self.activityIndicator.stopAnimating()
self.activityIndicator.isHidden = true
}
})
}
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
guard let indexPath = collectionView?.indexPathForItem(at: location) else { return nil }
guard let cell = collectionView?.cellForItem(at: indexPath) else { return nil }
guard let detailVC = storyboard?.instantiateViewController(withIdentifier: "gridDetailedView") as? gridDetailedViewController else { return nil }
//let photo = UIImage(named: "Rectangle")
detailVC.imageURL = URLArrayStringThisSeason[indexPath.row]
print(URLArrayStringThisSeason[indexPath.row])
detailVC.preferredContentSize = CGSize(width: 300, height: 300)
previewingContext.sourceRect = cell.frame
print("peek")
return detailVC
}
func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
self.navigationController?.show(viewControllerToCommit, sender: Any?.self)
print("pop")
}
}
when peeking and popping, the function is supposed to send the imageURL to the detailed view controller and AlamofireImage will handle the image download and loading.
However, I've been getting misalignment issues with the collections view as the source rect will appear slightly above the cells and preventing peek and pop at certain parts of the cell. I think that this could be the cause of the peek and pop crash too.
edit:
here's what happens when I try to do peek and pop, you can see the focus of the cell is slightly shifted on top.
Ok I fixed the misalignment problem by changing this registerForPreviewingWithDelegate(self, sourceView: view)
to this
registerForPreviewingWithDelegate(self, sourceView: self.collectionView!)
however, the app is still crashes everytime I try to peek and pop.
Edit:
Ok the other problem is pretty much just some errors in the code. Just follow the above to fix the misalignment problem.

ReloadData in UICollectionView

I use CoreData and want to reload the UICollectionView when I save a new entry in CoreData but the only self.collectionview.reloaddata()don´t run it reload nothing.
Thanks for your Help.
My CoreData and UICollectionView I have try it to reload the UICollectionView when clockblogalarm become a new entry:
var clockblogalarm = [ClockBlogAlarm] () {
didSet { //self.ClockCollectionView.reloadData()
}
}
var mgdContext = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext
func loadClockAlarms () {
let LoadRequestBlogAlarm: NSFetchRequest<ClockBlogAlarm> = ClockBlogAlarm.fetchRequest()
clockblogalarm = try! mgdContext.fetch(LoadRequestBlogAlarm as! NSFetchRequest<NSFetchRequestResult>) as! [ClockBlogAlarm]
}
#IBOutlet var ClockCollectionView: UICollectionView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.loadClockAlarms()
}
Here I save the Entry in CoreData. When I click the Button Done view.bottomButtonHandler{} then I want to reload the data and show it in the UICollectionView with loadClockAlarms() .
else if index == 1 {
let view = AddClockView.instantiateFromNib()
let window = UIApplication.shared.delegate?.window!
view.closeButtonHandler = {[weak modal] in
modal?.closeWithLeansRandom()
return
}
view.bottomButtonHandler = {[weak modal] in
modal?.closeWithLeansRandom()
self.loadClockAlarms()
return
}
modal.show(modalView: view, inView: window!)
}
// MARK: UICollectionViewDataSource
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return clockblogalarm.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AlarmBlogCell", for: indexPath) as! AlarmBlogCell
cell.layer.cornerRadius = 8
let clockblogalarms = clockblogalarm[indexPath.row]
if clockblogalarms.lock == "ClockblogAlarm" {
cell.Title.text = clockblogalarms.title
cell.Time.text = "SSSSS"
}
return cell
}
swift 5 & 4.2:
DispatchQueue.main.async {
self.collectionView.reloadData()
}
Ensure the reloadData() is called in the main thread, as it is the UI-responsible action:
dispatch_async(dispatch_get_main_queue()) {
self.ClockCollectionView.reloadData()
}

Tap on cell and get index

When i tap on a cell i want to receive the index or other identifier specific to that cell. The code works and goes in the tapped function. But how can i receive a index or something like that?
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("ShowCell", forIndexPath: indexPath) as UICollectionViewCell
if cell.gestureRecognizers?.count == nil {
let tap = UITapGestureRecognizer(target: self, action: "tapped:")
tap.allowedPressTypes = [NSNumber(integer: UIPressType.Select.rawValue)]
cell.addGestureRecognizer(tap)
}
return cell
}
func tapped(sender: UITapGestureRecognizer) {
print("tap")
}
Swift 4 - 4.2 - Returns index of tapped cell.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cellIndex = indexPath[1] // try without [1] returns a list.
print(indexPath[1])
chapterIndexDelegate.didTapChapterSelection(chapterIndex: test)
}
Build on Matts answer
first off we add the tap recognizer
let tap = UITapGestureRecognizer(target: self, action: #selector(self.tapped(tapGestureRecognizer:)))
cell.textView.addGestureRecognizer(tap)
Then we use the following function to get the indexPath
#objc func tapped(tapGestureRecognizer: UITapGestureRecognizer){
//textField or what ever view you decide to have the tap recogniser on
if let textField = tapGestureRecognizer.view as? UITextField {
// get the cell from the textfields superview.superview
// textField.superView would return the content view within the cell
if let cell = textField.superview?.superview as? UITableViewCell{
// tableview we defined either in storyboard or globally at top of the class
guard let indexPath = self.tableView.indexPath(for: cell) else {return}
print("index path =\(indexPath)")
// then finally if you wanted to pass the indexPath to the main tableView Delegate
self.tableView(self.tableView, didSelectRowAt: indexPath)
}
}
}
Think about it. The sender is the tap gesture recognizer. The g.r.'s view is the cell. Now you can ask the collection view what the index path of this cell is (indexPathForCell:).