UICollectionViewで削除するために、UITableViewのスワイプと同じ動作をどのように実装できるかを質問したいだけです。チュートリアルを探していますが、見つかりません。
また、iOS 5をサポートするためにPSTCollectionViewラッパーを使用しています。
ありがとうございました!
編集:スワイプ認識機能は既に優れています。必要なのは、削除モードをキャンセルするときのUITableViewと同じ機能です。ユーザーがセルまたはテーブルビューの空白スペースをタップしたとき(つまり、ユーザーが[削除]ボタンの外側をタップしたとき)。 UITapGestureRecognizerは、タッチのリリース時にタップを検出するだけなので、機能しません。 UITableViewは、ジェスチャーの開始時(リリース時ではなく)のタッチを検出し、すぐに削除モードをキャンセルします。
非常にシンプルです。customContentView
の後ろにcustomBackgroundView
とcustomContentView
を追加する必要があります。
その後、ユーザーが右から左にスワイプすると、customContentView
を左にシフトする必要があります。ビューをシフトすると、customBackgroundView
に表示されます。
Let Code:
まず、UICollectionView
にpanGestureを追加する必要があります
override func viewDidLoad() {
super.viewDidLoad()
self.panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panThisCell))
panGesture.delegate = self
self.collectionView.addGestureRecognizer(panGesture)
}
セレクタを次のように実装します
func panThisCell(_ recognizer:UIPanGestureRecognizer){
if recognizer != panGesture{ return }
let point = recognizer.location(in: self.collectionView)
let indexpath = self.collectionView.indexPathForItem(at: point)
if indexpath == nil{ return }
guard let cell = self.collectionView.cellForItem(at: indexpath!) as? CustomCollectionViewCell else{
return
}
switch recognizer.state {
case .began:
cell.startPoint = self.collectionView.convert(point, to: cell)
cell.startingRightLayoutConstraintConstant = cell.contentViewRightConstraint.constant
if swipeActiveCell != cell && swipeActiveCell != nil{
self.resetConstraintToZero(swipeActiveCell!,animate: true, notifyDelegateDidClose: false)
}
swipeActiveCell = cell
case .changed:
let currentPoint = self.collectionView.convert(point, to: cell)
let deltaX = currentPoint.x - cell.startPoint.x
var panningleft = false
if currentPoint.x < cell.startPoint.x{
panningleft = true
}
if cell.startingRightLayoutConstraintConstant == 0{
if !panningleft{
let constant = max(-deltaX,0)
if constant == 0{
self.resetConstraintToZero(cell,animate: true, notifyDelegateDidClose: false)
}else{
cell.contentViewRightConstraint.constant = constant
}
}else{
let constant = min(-deltaX,self.getButtonTotalWidth(cell))
if constant == self.getButtonTotalWidth(cell){
self.setConstraintsToShowAllButtons(cell,animate: true, notifyDelegateDidOpen: false)
}else{
cell.contentViewRightConstraint.constant = constant
cell.contentViewLeftConstraint.constant = -constant
}
}
}else{
let adjustment = cell.startingRightLayoutConstraintConstant - deltaX;
if (!panningleft) {
let constant = max(adjustment, 0);
if (constant == 0) {
self.resetConstraintToZero(cell,animate: true, notifyDelegateDidClose: false)
} else {
cell.contentViewRightConstraint.constant = constant;
}
} else {
let constant = min(adjustment, self.getButtonTotalWidth(cell));
if (constant == self.getButtonTotalWidth(cell)) {
self.setConstraintsToShowAllButtons(cell,animate: true, notifyDelegateDidOpen: false)
} else {
cell.contentViewRightConstraint.constant = constant;
}
}
cell.contentViewLeftConstraint.constant = -cell.contentViewRightConstraint.constant;
}
cell.layoutIfNeeded()
case .cancelled:
if (cell.startingRightLayoutConstraintConstant == 0) {
self.resetConstraintToZero(cell,animate: true, notifyDelegateDidClose: true)
} else {
self.setConstraintsToShowAllButtons(cell,animate: true, notifyDelegateDidOpen: true)
}
case .ended:
if (cell.startingRightLayoutConstraintConstant == 0) {
//Cell was opening
let halfOfButtonOne = (cell.swipeView.frame).width / 2;
if (cell.contentViewRightConstraint.constant >= halfOfButtonOne) {
//Open all the way
self.setConstraintsToShowAllButtons(cell,animate: true, notifyDelegateDidOpen: true)
} else {
//Re-close
self.resetConstraintToZero(cell,animate: true, notifyDelegateDidClose: true)
}
} else {
//Cell was closing
let buttonOnePlusHalfOfButton2 = (cell.swipeView.frame).width
if (cell.contentViewRightConstraint.constant >= buttonOnePlusHalfOfButton2) {
//Re-open all the way
self.setConstraintsToShowAllButtons(cell,animate: true, notifyDelegateDidOpen: true)
} else {
//Close
self.resetConstraintToZero(cell,animate: true, notifyDelegateDidClose: true)
}
}
default:
print("default")
}
}
制約を更新するヘルパーメソッド
func getButtonTotalWidth(_ cell:CustomCollectionViewCell)->CGFloat{
let width = cell.frame.width - cell.swipeView.frame.minX
return width
}
func resetConstraintToZero(_ cell:CustomCollectionViewCell, animate:Bool,notifyDelegateDidClose:Bool){
if (cell.startingRightLayoutConstraintConstant == 0 &&
cell.contentViewRightConstraint.constant == 0) {
//Already all the way closed, no bounce necessary
return;
}
cell.contentViewRightConstraint.constant = -kBounceValue;
cell.contentViewLeftConstraint.constant = kBounceValue;
self.updateConstraintsIfNeeded(cell,animated: animate) {
cell.contentViewRightConstraint.constant = 0;
cell.contentViewLeftConstraint.constant = 0;
self.updateConstraintsIfNeeded(cell,animated: animate, completionHandler: {
cell.startingRightLayoutConstraintConstant = cell.contentViewRightConstraint.constant;
})
}
cell.startPoint = CGPoint()
swipeActiveCell = nil
}
func setConstraintsToShowAllButtons(_ cell:CustomCollectionViewCell, animate:Bool,notifyDelegateDidOpen:Bool){
if (cell.startingRightLayoutConstraintConstant == self.getButtonTotalWidth(cell) &&
cell.contentViewRightConstraint.constant == self.getButtonTotalWidth(cell)) {
return;
}
cell.contentViewLeftConstraint.constant = -self.getButtonTotalWidth(cell) - kBounceValue;
cell.contentViewRightConstraint.constant = self.getButtonTotalWidth(cell) + kBounceValue;
self.updateConstraintsIfNeeded(cell,animated: animate) {
cell.contentViewLeftConstraint.constant = -(self.getButtonTotalWidth(cell))
cell.contentViewRightConstraint.constant = self.getButtonTotalWidth(cell)
self.updateConstraintsIfNeeded(cell,animated: animate, completionHandler: {(check) in
cell.startingRightLayoutConstraintConstant = cell.contentViewRightConstraint.constant;
})
}
}
func setConstraintsAsSwipe(_ cell:CustomCollectionViewCell, animate:Bool,notifyDelegateDidOpen:Bool){
if (cell.startingRightLayoutConstraintConstant == self.getButtonTotalWidth(cell) &&
cell.contentViewRightConstraint.constant == self.getButtonTotalWidth(cell)) {
return;
}
cell.contentViewLeftConstraint.constant = -self.getButtonTotalWidth(cell) - kBounceValue;
cell.contentViewRightConstraint.constant = self.getButtonTotalWidth(cell) + kBounceValue;
self.updateConstraintsIfNeeded(cell,animated: animate) {
cell.contentViewLeftConstraint.constant = -(self.getButtonTotalWidth(cell))
cell.contentViewRightConstraint.constant = self.getButtonTotalWidth(cell)
self.updateConstraintsIfNeeded(cell,animated: animate, completionHandler: {(check) in
cell.startingRightLayoutConstraintConstant = cell.contentViewRightConstraint.constant;
})
}
}
func updateConstraintsIfNeeded(_ cell:CustomCollectionViewCell, animated:Bool,completionHandler:@escaping ()->()) {
var duration:Double = 0
if animated{
duration = 0.1
}
UIView.animate(withDuration: duration, delay: 0, options: [.curveEaseOut], animations: {
cell.layoutIfNeeded()
}, completion:{ value in
if value{ completionHandler() }
})
}
サンプルプロジェクトを作成しました here in Swift 3。
これは チュートリアル の修正版です。
ジェスチャーレコグナイザーの使用を回避する問題に対するより簡単な解決策があります。ソリューションは、UIScrollView
とUIStackView
の組み合わせに基づいています。
まず、2つのコンテナビューを作成する必要があります。1つはセルの表示部分用で、もう1つは非表示部分用です。これらのビューをUIStackView
に追加します。 stackView
はコンテンツビューとして機能します。ビューの幅がstackView.distribution = .fillEqually
と等しいことを確認してください。
ページングが有効になっているstackView
内にUIScrollView
を埋め込みます。 scrollView
はセルの端に制限する必要があります。次に、stackView
の幅をscrollView
の幅の2倍に設定して、各コンテナビューにセルの幅を設定します。
この単純な実装で、表示および非表示のビューを持つ基本セルを作成しました。表示ビューを使用してセルにコンテンツを追加し、非表示ビューで削除ボタンを追加できます。これにより、これを実現できます。
GitHubのサンプルプロジェクト を設定しました。 このソリューションの詳細についてはこちらをご覧ください 。
このソリューションの最大の利点は、シンプルさであり、制約やジェスチャー認識機能に対処する必要がないことです。
@JacekLampartへの同様のアプローチに従いましたが、UICollectionViewCellのawakeFromNib関数にUISwipeGestureRecognizerを追加することを決定したので、一度だけ追加されます。
UICollectionViewCell.m
- (void)awakeFromNib {
UISwipeGestureRecognizer* swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeToDeleteGesture:)];
swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
[self addGestureRecognizer:swipeGestureRecognizer];
}
- (void)swipeToDeleteGesture:(UISwipeGestureRecognizer *)swipeGestureRecognizer {
if (swipeGestureRecognizer.state == UIGestureRecognizerStateEnded) {
// update cell to display delete functionality
}
}
削除モードの終了については、UIViewのNSArrayを使用してカスタムUIGestureRecognizerを作成しました。私はこの質問から@iMSからアイデアを借りました: ITapGestureRecognizer-タッチアップではなくタッチダウンで機能させるか?
TouchesBeganで、タッチポイントがどのUIViews内にもない場合、ジェスチャーは成功し、削除モードが終了します。
このようにして、セル内の削除ボタン(およびその他のビュー)をUIGestureRecognizerに渡すことができます。タッチポイントがボタンのフレーム内にある場合、削除モードは終了しません。
TouchDownExlusionViewsGestureRecognizer.h
#import <UIKit/UIKit.h>
@interface TouchDownExcludingViewsGestureRecognizer : UIGestureRecognizer
@property (nonatomic) NSArray *excludeViews;
@end
TouchDownExlusionViewsGestureRecognizer.m
#import "TouchDownExcludingViewsGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
@implementation TouchDownExcludingViewsGestureRecognizer
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.state == UIGestureRecognizerStatePossible) {
BOOL touchHandled = NO;
for (UIView *view in self.excludeViews) {
CGPoint touchLocation = [[touches anyObject] locationInView:view];
if (CGRectContainsPoint(view.bounds, touchLocation)) {
touchHandled = YES;
break;
}
}
self.state = (touchHandled ? UIGestureRecognizerStateFailed : UIGestureRecognizerStateRecognized);
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
self.state = UIGestureRecognizerStateFailed;
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.state = UIGestureRecognizerStateFailed;
}
@end
実装(UICollectionViewを含むUIViewController内):
#import "TouchDownExcludingViewsGestureRecognizer.h"
TouchDownExcludingViewsGestureRecognizer *touchDownGestureRecognizer = [[TouchDownExcludingViewsGestureRecognizer alloc] initWithTarget:self action:@selector(exitDeleteMode:)];
touchDownGestureRecognizer.excludeViews = @[self.cellInDeleteMode.deleteButton];
[self.view addGestureRecognizer:touchDownGestureRecognizer];
- (void)exitDeleteMode:(TouchDownExcludingViewsGestureRecognizer *)touchDownGestureRecognizer {
// exit delete mode and disable or remove TouchDownExcludingViewsGestureRecognizer
}
次のように、UISwipeGestureRecognizerを各コレクションセルに追加してみてください。
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CollectionViewCell *cell = ...
UISwipeGestureRecognizer* gestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(userDidSwipe:)];
[gestureRecognizer setDirection:UISwipeGestureRecognizerDirectionRight];
[cell addGestureRecognizer:gestureRecognizer];
}
に続く:
- (void)userDidSwipe:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
//handle the gesture appropriately
}
}