Проведите по экрану в CollectionView
Я пытаюсь реплицировать салфетки для удаления функции, как в почтовом представлении таблицы. Только на этот раз мне нужно собрать его в виде коллекции, но мне немного тяжело. Это горизонтальный список прокрутки с удалением, чтобы удалить. У меня уже есть прокрутка вверх, но с трудом выясняется, как мне нужно настроить салфетки, чтобы удалить/коснуться, чтобы удалить или проигнорировать функциональность.
Он выглядит следующим образом:
![Удалить изображение]()
Итак, я использую следующую коллекцию:
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 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.
}
}
Но проблема начинается там. Как только моя ячейка находится за пределами границ коллекции, я больше не могу ее получить. Я все еще хочу провести его дальше, чтобы удалить его. Я могу сделать это только, щелкнув по кнопке удаления, но я хочу, чтобы Imageview над ним также был прокручиваем. Или, если я коснусь изображения вне коллекции, он должен вернуться в строку и не удалить его.
Если я увеличиваю границы коллекции, я могу предотвратить эту проблему, но я также могу прокрутить, чтобы удалить за пределами видимой высоты ячейки. Это вызвано ссылкой tapLocation, которая находится внутри коллекции и обнаруживает indexPath. Что-то, чего я не хочу. Я хочу, чтобы проведите по экрану только для работы с ячейкой коллекции.
Также кнопка и изображение мешают друг другу, потому что я не могу их отличить. Они оба находятся в одной и той же ячейке, поэтому мне интересно, есть ли у меня кнопка удаления в ячейке. Или где я должен разместить его иначе? Я мог бы также сделать из него две кнопки и отключить взаимодействие с пользователем в зависимости от состояния, но не знаю, как это закончится.
Ответы
Ответ 1
Ради собственного любопытства я попытался воспроизвести то, что вы пытаетесь сделать, и заставил его работать как-то хорошо. Он отличается от вашего в том, как я настраиваю жесты смахивания, поскольку я не использовал панорамирование, но вы сказали, что у вас уже была эта часть, и вы не тратили на нее слишком много времени. Очевидно, что панорамирование - более надежное решение, чтобы сделать его интерактивным, но его вычисление занимает немного больше времени, но эффект и обработка этого не должны сильно отличаться от моего примера.
Чтобы решить проблему, связанную с невозможностью провести вне ячейки, я решил проверить, была ли точка в прямоугольнике со смахиванием, что в два раза больше высоты прямоугольника без пролистывания, например:
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!])
}
Я создал демонстрацию с комментариями: https://github.com/imbue11235/swipeToDeleteCell
Я надеюсь, что это поможет вам в любом случае!
Ответ 2
Итак, если вы хотите, чтобы распознаватель жестов распознавания продолжал записывать движение, когда он находится вне своего вида коллекции, вам необходимо прикрепить его к родительскому объекту коллекции, поэтому он ограничен всей областью, в которой пользователь может прокручивать.
Это означает, что вы получите подсказки для вещей вне представления коллекции, но вы можете легко игнорировать тех, кто использует любое количество методов.
Чтобы зарегистрировать кнопки удаления кнопки, вам нужно вызвать addTarget: действие: forControlEvents: на кнопке
Я бы сохранил ячейку, как у вас, с изображением и кнопкой вместе. Нам будет намного легче управлять, и они будут вместе.
Чтобы управлять перемещением изображения вверх и вниз, я бы посмотрел на использование преобразования или NSLayoutConstraint. Затем вам нужно настроить одно значение, чтобы оно перемещалось вверх и вниз в синхронизации с пользовательскими прогибами. Не вмешиваться в рамки.
Ответ 3
Если вы хотите сделать его общим:
Сделать костюм Swipeable View:
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")
}
}
Пример 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
}
}