web-dev-qa-db-fra.com

Faites glisser pour supprimer sur CollectionView

J'essaie de reproduire le balayage pour supprimer la fonctionnalité d'iOS. Je sais qu'il est instantanément disponible sur une vue de table, mais l'interface utilisateur dont j'ai besoin pour créer des avantages à partir d'une vue de collection. Pour cela, j'ai besoin d'une implémentation personnalisée où j'utiliserais un geste de balayage vers le haut. Heureusement, c'est quelque chose que j'ai réussi à implémenter moi-même, mais j'ai du mal à comprendre comment je dois configurer le balayage pour supprimer/toucher pour supprimer/ignorer la fonctionnalité.

L'interface utilisateur ressemble actuellement à ceci: Delete Image

J'utilise donc la collectionview suivante:

func buildCollectionView() {
    let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
    layout.scrollDirection = .horizontal
    layout.minimumInteritemSpacing = 0;
    layout.minimumLineSpacing = 4;

    collectionView = UICollectionView(frame: CGRect(x: 0, y: screenSize.midY - 120, width: screenSize.width, height: 180), collectionViewLayout: layout)

    collectionView.dataSource = self
    collectionView.delegate = self
    collectionView.register(VideoCell.self, forCellWithReuseIdentifier: "videoCell")
    collectionView.showsHorizontalScrollIndicator = false
    collectionView.showsVerticalScrollIndicator = false
    collectionView.contentInset = UIEdgeInsetsMake(0, 20, 0, 30)
    collectionView.backgroundColor = UIColor.white()
    collectionView.alpha = 0.0


    //can swipe cells outside collectionview region
    collectionView.layer.masksToBounds = false


    swipeUpRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.deleteCell))
    swipeUpRecognizer.delegate = self

    collectionView.addGestureRecognizer(swipeUpRecognizer)
    collectionView.isUserInteractionEnabled = true
}

Ma cellule vidéo personnalisée contient une image et en dessous se trouve le bouton Supprimer. Donc, si vous faites glisser l'image vers le haut, le bouton de suppression apparaît. Je ne sais pas si c'est la bonne façon de procéder:

class VideoCell : UICollectionViewCell {
var deleteView: UIButton!
var imageView: UIImageView!

override init(frame: CGRect) {
    super.init(frame: frame)

    deleteView = UIButton(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height))
    deleteView.contentMode = UIViewContentMode.scaleAspectFit
    contentView.addSubview(deleteView)

    imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height))
    imageView.contentMode = UIViewContentMode.scaleAspectFit
    contentView.addSubview(imageView)


}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}
}

Et j'utilise la logique suivante:

func deleteCell(sender: UIPanGestureRecognizer) {
    let tapLocation = sender.location(in: self.collectionView)
    let indexPath = self.collectionView.indexPathForItem(at: tapLocation)

    if velocity.y < 0 { 
        //detect if there is a swipe up and detect it's distance. If the distance is far enough we snap the cells Imageview to the top otherwise we drop it back down. This works fine already.
    }
}

Mais le problème commence là. Dès que ma cellule est en dehors des limites de collectionview, je ne peux plus y accéder. Je veux toujours le faire glisser pour le supprimer. Je ne peux le faire qu'en glissant sur le bouton Supprimer, mais je veux que l'image au-dessus soit également glissable. Ou si j'appuie sur l'image en dehors de la vue de la collection, elle doit glisser de nouveau dans la ligne et ne pas la supprimer.

Si j'augmente les limites de collectionview, je peux éviter ce problème, mais je peux également balayer pour supprimer en dehors de la hauteur visible de la cellule. Cela est dû à la tapLocation qui se trouve dans la collectionview et détecte un indexPath. Quelque chose que je ne veux pas. Je souhaite que le balayage vers le haut ne fonctionne que sur la cellule d'une vue de collection.

De plus, le bouton et l'image interfèrent l'un avec l'autre parce que je ne peux pas les distinguer. Ils sont tous les deux dans la même cellule, c'est pourquoi je me demande si je devrais avoir le bouton Supprimer dans la cellule. Ou où dois-je le placer autrement? Je pourrais également en faire deux boutons et désactiver l'interaction de l'utilisateur en fonction de l'état, mais je ne sais pas comment cela finirait.

23
Wouter125

Pour ma propre curiosité, j'ai essayé de faire une réplique de ce que vous essayez de faire, et je l'ai fait fonctionner d'une manière ou d'une autre. Il diffère du vôtre dans la façon dont je configure les gestes de balayage car je n'ai pas utilisé le panoramique, mais vous avez dit que vous aviez déjà cette partie et que vous n'y aviez pas consacré trop de temps. Pan est évidemment la solution la plus solide pour le rendre interactif, mais prend un peu plus de temps à calculer, mais son effet et sa manipulation ne devraient pas différer beaucoup de mon exemple.

Pour résoudre le problème de ne pas pouvoir glisser à l'extérieur de la cellule, j'ai décidé de vérifier si le point était dans le recto glissé, qui est deux fois la hauteur du recto non glissé comme ceci:

            let cellFrame = activeCell.frame
            let rect = CGRectMake(cellFrame.Origin.x, cellFrame.Origin.y - cellFrame.height, cellFrame.width, cellFrame.height*2)
            if CGRectContainsPoint(rect, point) {
                // If swipe point is in the cell delete it

                let indexPath = myView.indexPathForCell(activeCell)
                cats.removeAtIndex(indexPath!.row)
                myView.deleteItemsAtIndexPaths([indexPath!])

            }

J'ai créé une démonstration avec des commentaires: https://github.com/imbue11235/swipeToDeleteCell

J'espère que cela vous aidera de toute façon!

4
Imbue

Donc, si vous souhaitez que le reconnaisseur de mouvements de balayage continue d'enregistrer les mouvements lorsqu'ils se trouvent en dehors de leur vue de collection, vous devez l'attacher au parent de la vue de collection, de sorte qu'il soit limité à la zone complète où l'utilisateur peut glisser.

Cela signifie que vous obtiendrez des balayages pour des choses en dehors de la vue de la collection, mais vous pouvez facilement ignorer celles qui utilisent un certain nombre de techniques.

Pour enregistrer les taps du bouton Supprimer, vous devrez appeler addTarget: action: forControlEvents: sur le bouton

Je garderais la cellule telle que vous l'avez, avec l'image et le bouton ensemble. Ce sera beaucoup plus facile à gérer et ils vont de pair.

Pour gérer le déplacement de l'image vers le haut et vers le bas, je chercherais à utiliser une transformation ou une NSLayoutConstraint. Ensuite, il vous suffit d'ajuster une valeur pour la faire monter et descendre en synchronisation avec les balayages utilisateur. Pas de problème avec les cadres.

9
Michael

Si vous voulez le rendre générique:

Faire un costume Vue glissable:

import UIKit

class SwipeView: UIView {
lazy var label: UILabel = {
    let label = UILabel()
    label.textColor = .black
    label.backgroundColor = .green
    return label
}()

let visableView = UIView()
var originalPoint: CGPoint!
var maxSwipe: CGFloat! = 50 {
    didSet(newValue) {
        maxSwipe = newValue
    }
}

@IBInspectable var swipeBufffer: CGFloat = 2.0
@IBInspectable var highVelocity: CGFloat = 300.0

private let originalXCenter: CGFloat = UIScreen.main.bounds.width / 2
private var panGesture: UIPanGestureRecognizer!

public var isPanGestureEnabled: Bool {
    get { return panGesture.isEnabled }
    set(newValue) {
        panGesture.isEnabled = newValue
    }
}

override init(frame: CGRect) {
    super.init(frame: frame)
    setupViews()
    setupGesture()
}

private func setupViews() {
    addSubview(visableView)
    visableView.addSubview(label)

    visableView.edgesToSuperview()
    label.edgesToSuperview()

}

private func setupGesture() {
    panGesture = UIPanGestureRecognizer(target: self, action: #selector(swipe(_:)))
    panGesture.delegate = self
    addGestureRecognizer(panGesture)
}

@objc func swipe(_ sender:UIPanGestureRecognizer) {
    let translation = sender.translation(in: self)
    let newXPosition = center.x + translation.x
    let velocity = sender.velocity(in: self)

    switch(sender.state) {

    case .changed:
        let shouldSwipeRight = translation.x > 0 && newXPosition < originalXCenter
        let shouldSwipeLeft = translation.x < 0 && newXPosition > originalXCenter - maxSwipe
        guard shouldSwipeRight || shouldSwipeLeft else { break }
            center.x = newXPosition
    case .ended:
        if -velocity.x > highVelocity {
            center.x = originalXCenter - maxSwipe
            break
        }
        guard center.x > originalXCenter - maxSwipe - swipeBufffer, center.x < originalXCenter - maxSwipe + swipeBufffer, velocity.x < highVelocity  else {
            center.x = originalXCenter
            break
        }
    default:
        break
    }
    panGesture.setTranslation(.zero, in: self)
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

}

extension SwipeView: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}
}

La vue permutable incorporée dans UICollectionViewCell:

import UIKit
import TinyConstraints

protocol DeleteCellDelegate {
func deleteCell(_ sender : UIButton)
}

class SwipeableCell: UICollectionViewCell {

lazy var deleteButton: UIButton = {
    let button = UIButton()
    button.backgroundColor = .red
    button.addTarget(self, action: #selector(didPressedButton(_:)), for: .touchUpInside)
    button.titleLabel?.text = "Delete"
    return button
}()

var deleteCellDelegate: DeleteCellDelegate?

@objc private func didPressedButton(_ sender: UIButton) {
    deleteCellDelegate?.deleteCell(sender)
    print("delete")
}
let swipeableview: SwipeView = {
    return SwipeView()
}()

override init(frame: CGRect) {
    super.init(frame: frame)
    addSubview(deleteButton)
    addSubview(swipeableview)
    swipeableview.edgesToSuperview()

    deleteButton.edgesToSuperview(excluding: .left, usingSafeArea: true)
    deleteButton.width(bounds.width * 0.3)
    swipeableview.maxSwipe = deleteButton.bounds.width

}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}
}

Un exemple de ViewController:

import UIKit
import TinyConstraints

class ViewController: UIViewController, DeleteCellDelegate {

func deleteCell(_ sender: UIButton) {
    let indexPath = IndexPath(item: sender.tag, section: 0)
    items.remove(at: sender.tag)
    collectionView.deleteItems(at: [indexPath])
}

lazy var collectionView: UICollectionView = {
    let layout = UICollectionViewFlowLayout()
    layout.itemSize = CGSize(width: view.bounds.width, height: 40)
    layout.sectionInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
    let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
    cv.backgroundColor = .yellow
    cv.isPagingEnabled = true
    cv.isUserInteractionEnabled = true
    return cv
}()

var items  = ["1", "2", "3"]

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(collectionView)
    collectionView.delegate = self
    collectionView.dataSource = self
    collectionView.edgesToSuperview(usingSafeArea: true)
    collectionView.register(SwipeableCell.self, forCellWithReuseIdentifier: "cell")
    let panGesture = UIPanGestureRecognizer()
    view.addGestureRecognizer(panGesture)
    panGesture.delegate = self
}

}

extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return items.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SwipeableCell
    cell.backgroundColor = .blue
    cell.swipeableview.label.text = items[indexPath.item]
    cell.deleteButton.tag = indexPath.item
    cell.deleteCellDelegate = self
    return cell
}

func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
}

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}

}

extension ViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return false
}
}
0
Tal Spektor