【问题标题】:Swipe to delete on CollectionView在 CollectionView 上滑动删除
【发布时间】:2016-12-12 17:12:02
【问题描述】:

我正在尝试复制 iOS 的滑动删除功能。我知道它可以立即在 tableview 上使用,但是我需要构建的 UI 从 Collection View 中受益。因此,我需要一个自定义实现,我将使用向上滑动手势。幸运的是,这是我自己设法实现的,但是我很难弄清楚我需要如何设置滑动删除/点击删除/忽略功能。

用户界面当前如下所示:

所以我正在使用以下集合视图:

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
}

我的自定义视频单元包含一个图像,其下方是删除按钮。因此,如果您向上滑动图像,则会弹出删除按钮。不确定这是否是正确的方法:

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

我正在使用以下逻辑:

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

但问题从这里开始。一旦我的单元格超出 collectionview 边界,我就无法再访问它了。我仍然想进一步滑动它以将其删除。我只能通过滑动删除按钮来做到这一点,但我希望它上面的 Imageview 也可以滑动。或者,如果我点击 collectionview 之外的图像,它应该滑回该行而不是删除它。

如果我增加 collectionview 边界,我可以防止这个问题,但我也可以滑动以移除单元格的可见高度之外。这是由 collectionview 中的 tapLocation 引起的,它检测到 indexPath。我不想要的东西。我希望向上滑动只适用于 collectionview 的单元格。

按钮和图像也会相互干扰,因为我无法区分它们。它们都在同一个单元格中,这就是为什么我想知道是否应该在单元格中设置删除按钮。或者我应该把它放在哪里?我还可以用它制作两个按钮并根据状态禁用用户交互,但不确定最终会如何。

【问题讨论】:

    标签: ios swift uipangesturerecognizer collectionview


    【解决方案1】:

    因此,如果您希望滑动手势识别器在其位于集合视图之外时继续记录移动,则需要将其附加到集合视图的父级,因此它会限制在用户可以滑动的整个区域.

    这确实意味着您会在集合视图之外获得滑动,但您可以很容易地忽略使用任意数量的技术的那些。

    要注册删除按钮点击,您需要在按钮上调用 addTarget:action:forControlEvents:

    我会将单元格保持原样,将图像和按钮放在一起。这将更容易管理,并且它们属于一起。

    要管理上下移动图像,我会考虑使用变换或 NSLayoutConstraint。然后,您只需调整一个值,使其与用户滑动同步上下移动。不要乱用框架。

    【讨论】:

    • 让我跑下每条线。如果我将它附加到父视图,就像你说的那样,我会到处刷卡。问题是一旦我的单元格在我的collectionview 之外,我在deletecell 函数中的indexpath 将变为nil,我不能再拖动单元格了。增加我的collectionview 大小将使其成为可能,但它也将使用户能够在他们甚至没有触摸图像本身时拖动单元格。删除操作可以很容易地修复。唯一的问题是,图像视图的操作与删除按钮不同。当它们都可见时如何组合这两个动作?
    • 我不确定,我想禁用集合视图剪辑到边界将允许您在其父级之外转换图像视图。 myCollectionView.clipsToBounds = false
    • 不,这不会奏效,因为如果视图在其范围内,视图只会将“触地”事件转发到子视图(collectionview 内的单元格)。所以增加界限会起作用,但这不是我想要的。还有其他建议吗?我可以覆盖 pointInside 方法,但这是我不想要的,因为我认为它不会返回我的 indexpath。
    • 好吧,也许,正如您所说,让 UICollectionView 与可滑动区域一样大,但使用自定义 UICollectionViewLayout 将单元格保持在其原始位置,或者使用 UIScrollView 父类中的 contentInset。
    【解决方案2】:

    出于我自己的好奇心,我尝试复制您正在尝试做的事情,并让它以某种方式运作良好。它与您设置滑动手势的方式不同,因为我没有使用平移,但您说您已经拥有该部分,并且没有花费太多时间。 Pan 显然是让它交互的更可靠的解决方案,但计算时间更长,但它的效果和处理应该与我的示例相差不大。

    为了解决无法在单元格外滑动的问题,我决定检查该点是否在滑动矩形中,这是非滑动矩形高度的两倍,如下所示:

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

    我用 cmets 创建了一个演示:https://github.com/imbue11235/swipeToDeleteCell

    无论如何,我希望它对您有所帮助!

    【讨论】:

    • 我当然对您的解决方案感兴趣。你能把更新的版本推送到github吗,因为我目前只有一个空项目!
    • 哦,是的,这是一个错误,令人尴尬。现在更新了!
    • 像魅力一样工作。正是我想要的。已经为我的 pangesture 重写了它,但实际上我正在研究如何增加单元格面积 :)
    • 很高兴听到!很高兴我能帮忙:-)
    【解决方案3】:

    如果你想让它变得通用:

    制作服装可滑动视图:

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

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

    一个示例视图控制器:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-05
      • 1970-01-01
      • 1970-01-01
      • 2017-12-01
      • 2016-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多