最近自己看了一下iOS 8.0中的郵件刪除的功能,在iOS8.0中蘋(píng)果給tableView新增了一個(gè)在Cell上側(cè)滑,右邊彈出按鈕進(jìn)行便捷操作的一個(gè)新的代理方法。但是細(xì)心的話你會(huì)發(fā)現(xiàn),這個(gè)方法只提供類似QQ、微信中的那種單調(diào)的彈出菜單,并不能實(shí)現(xiàn)類似郵件中的從右一直往左拉到一定程度可以直接刪除的快捷操作。所以自己就想試著寫(xiě)一個(gè)這樣的小Demo,如果你不經(jīng)意間看到了,歡迎指證,不喜勿噴...
其實(shí)仔細(xì)的思考一下這個(gè)東西,并不是太有技術(shù)含量(ps:好吧, 承認(rèn)自己程度沒(méi)那么高,所以也想通過(guò)這種方式提高自己)。我大概的把要做的工作分了以下幾步:
- 首先自定義自己的Cell,Cell要提供兩個(gè)代理方法
- 獲取每一行Cell右邊將要顯示的按鈕集合
- 每一行Cell右邊按鈕被點(diǎn)擊后的回調(diào)
- 其次就是在Cell內(nèi)部要實(shí)現(xiàn)touchesBegan等一系列方法來(lái)監(jiān)測(cè)我們的拖拽手勢(shì)
- 在touchesMoved里面根據(jù)用戶拖拽手勢(shì)的點(diǎn)來(lái)動(dòng)態(tài)改變Cell中contentView和右邊按鈕的frame
下面我們首先創(chuàng)建自定義的一個(gè)Cell
#import <UIKit/UIKit.h>
#import "AGTableViewRowAction.h"
@protocol AGTableViewCellDelegate <NSObject>
@optional
/*!
* @brief 獲取每一行Cell對(duì)應(yīng)的按鈕集合
*
* @param tableView 父級(jí)tableView
* @param indexPath 索引
*
* @return 該行Cell的按鈕集合
*/
- (NSArray *)AGTableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath;
/*!
* @brief 每一行Cell的動(dòng)作觸發(fā)回調(diào)
*
* @param tableView 父級(jí)tableView
* @param index 點(diǎn)擊按鈕集合的動(dòng)作索引
* @param indexPath 索引
*/
- (void)AGTableView:(UITableView *)tableView didSelectActionIndex:(NSInteger)index forRowAtIndexPath:(NSIndexPath *)indexPath;
@end
@interface AGTableViewCell : UITableViewCell
/*!
* @brief 滑動(dòng)過(guò)程中刷新動(dòng)畫(huà)的時(shí)間間隔,默認(rèn)值是0.2s
*/
@property (nonatomic, assign) CGFloat dragAnimationDuration;
/*!
* @brief 重置動(dòng)畫(huà)的時(shí)長(zhǎng),默認(rèn)值是0.3s
*/
@property (nonatomic, assign) CGFloat resetAnimationDuration;
@property (nonatomic, assign) BOOL isEditing;
@property (nonatomic, weak) id<AGTableViewCellDelegate> delegate;
-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier inTableView:(UITableView *)tableView;
@end
這個(gè)類的實(shí)例化方法除了接收tableView的Cell的樣式以及復(fù)用標(biāo)示之外,還把其所屬的tableView作為氣的一個(gè)屬性傳過(guò)來(lái),目的是為了在用戶拖動(dòng)Cell的時(shí)候限制tableView的滾動(dòng)。
頭文件中的兩個(gè)設(shè)置動(dòng)畫(huà)時(shí)長(zhǎng)的屬性以及Cell的預(yù)設(shè)值需要在初始化方法中進(jìn)行如下設(shè)置
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self.contentView setBackgroundColor:[UIColor grayColor]];
self.tableView = tableView;
self.touchBeganPointX = 0.0f;
self.dragAnimationDuration = 0.2f;
self.resetAnimationDuration = 0.3f;
self.isEditing = NO;
_isMoving = NO;
_hasMoved = NO;
}
return self;
這里我使用了兩個(gè)臨時(shí)的局部變量_isMoving _hasMoved來(lái)在Cell被拖動(dòng)的時(shí)候標(biāo)示其狀態(tài),使用self.isEditing來(lái)判斷在當(dāng)前Cell上是應(yīng)該響應(yīng)一般touch事件還是響應(yīng)它的tableView的didSelectRowAtIndexPath事件。然后我們要在Cell內(nèi)部擁有一個(gè)可變的數(shù)組來(lái)存儲(chǔ)該行Cell右邊的action集合,然后最好在layoutSubviews的時(shí)候通過(guò)代理方法拿到這個(gè)集合。
- (void)getActionsArray {
self.indexPath = [self.tableView indexPathForCell:self];
if ([self.delegate respondsToSelector:@selector(AGTableView:editActionsForRowAtIndexPath:)]) {
self.actionButtons = [[self.delegate AGTableView:self.tableView editActionsForRowAtIndexPath:self.indexPath] mutableCopy];
CGFloat buttonWidth = (SCREENWIDTH / 2.0f) / self.actionButtons.count;
self.buttonWidth = buttonWidth;
for (AGTableViewRowAction *action in self.actionButtons) {
action.frame = CGRectMake(SCREENWIDTH, 0.0f, buttonWidth, self.height);
[action addTarget:self action:@selector(rightActionDidSelected:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:action];
}
}
}
注意最后[self addSubview:action],通常我們習(xí)慣在self.contentView上添加子View,但是記得不要把右邊的action添加到contentView上去。這樣的話在移動(dòng)contentView的時(shí)候會(huì)帶著右邊的action一同移動(dòng)。
這些工作做完接下來(lái)就是核心的處理邏輯,監(jiān)聽(tīng)用戶觸摸的手勢(shì):
首先要touchesBegan方法內(nèi)判斷touches.count是否為1,若不為1則調(diào)用父類的方法 [super touchesBegan:touches withEvent:event] ,若為1則記錄用戶開(kāi)始接觸到屏幕是的點(diǎn)的水平(x)軸的坐標(biāo),以備使用。
然后要在touchesMoved方法內(nèi)針對(duì)用戶觸摸的點(diǎn)與開(kāi)始觸摸屏幕是的點(diǎn)進(jìn)行對(duì)比并對(duì)當(dāng)期Cell的所有子View的frame進(jìn)行更改。
最后在touchesEnded方法中獲取到用戶最后觸摸的點(diǎn),然后來(lái)判定Cell當(dāng)前該執(zhí)行哪一種操作
1. 刪除自己
2. 恢復(fù)到展示右邊actions的狀態(tài)
3. 恢復(fù)到隱藏右邊actions的最初始的狀態(tài)
下面是touchesMoved中動(dòng)態(tài)改變Cell內(nèi)部子View的一段代碼
CGFloat currentLocationX = [touch locationInView:self.tableView].x;
CGFloat distance = (self.touchBeganPointX - currentLocationX) * 1.1;
if (distance > 0) { // 向左拉
CGFloat button_addWidth = (distance - (SCREENWIDTH / 2.0)) / self.actionButtons.count;
[UIView animateWithDuration:self.dragAnimationDuration animations:^{
self.contentView.left = -distance;
CGFloat t_dis = distance;
for (AGTableViewRowAction *action in self.actionButtons) {
if (distance > SCREENWIDTH / 2.0f) {
if (currentLocationX < 50) {
action.left = SCREENWIDTH - distance;
action.width = distance;
} else {
action.left = SCREENWIDTH - t_dis;
action.width = self.buttonWidth + button_addWidth;
}
} else {
action.left = SCREENWIDTH - t_dis;
}
t_dis = t_dis - distance / self.actionButtons.count;
}
}];
} else { // 向右拉
}
在這里我把distance作為用戶在Cell上拖動(dòng)的長(zhǎng)度,之所以在后邊乘以一個(gè)1.1,這其實(shí)是模擬一個(gè)彈性系數(shù),讓Cell的移動(dòng)距離稍稍的大于用戶手指拖動(dòng)的距離,而1.1的系數(shù)在真實(shí)顯示的情況下并不是很明顯,你可以適當(dāng)?shù)男薷倪@個(gè)數(shù)值達(dá)到自己理想的效果,從而獲得更好的用戶體驗(yàn)度。
我們用beganPointX減去currentPointX這樣得到的值若是大于0則證明用戶是在向左滑動(dòng),這樣從開(kāi)始滑動(dòng)就要開(kāi)始設(shè)置contentView的origin.x=-distance。然后遍歷右邊的actions數(shù)組,由于右邊所有按鈕總的初始寬度我預(yù)設(shè)的是當(dāng)前屏幕的一把,這樣也就是說(shuō),當(dāng)用戶滑動(dòng)的距離超過(guò)屏幕寬度一般的時(shí)候就要開(kāi)始修改每一個(gè)action的width。
那么在這里我設(shè)置的是當(dāng)用戶手指拉倒距離屏幕左邊距離小于50,并且總的拖動(dòng)長(zhǎng)度大于屏幕的一半時(shí),瞬間修改最右方的action的origin.x為用戶手指當(dāng)前位置的x軸上的位置,寬度增長(zhǎng)到當(dāng)前拖動(dòng)的長(zhǎng)度,然后當(dāng)用戶手指離開(kāi)屏幕的時(shí)候,根據(jù)contentView的origin.x的位置來(lái)選擇該如何重置或是刪除當(dāng)前Cell。
最后,關(guān)于在touchesEnded中如何做處理,以及其他的一些細(xì)節(jié)就不在此贅述了。如果你想看到更詳細(xì)的內(nèi)容可以去下載該項(xiàng)目的demo源碼。
注:此文章首發(fā)在簡(jiǎn)書(shū)轉(zhuǎn)載請(qǐng)說(shuō)明出處。
如果你想看到完整的代碼,可以去這里。