iOS之UITableView帶滑動操作菜單的Cell(下)

你同樣已經(jīng)學(xué)了不少關(guān)于這個 Cell 如何工作的知識漱凝;亦即,那個UITableViewCellScrollView
碍现,它包含 contentView 和 Disclosure Indicator (以及 Delete 按鈕庄撮,如果它被添加的話)徐钠,明顯是要做某些事 篱竭。你可能已經(jīng)從它的名字以及它是UIScrollView
的子類而猜到了力图。
你可以通過在tableView:cellForRowAtIndexPath:
下面添加一個簡單的for
循環(huán)來測試這個假設(shè),就在recursiveDescription
那一行下面:

for (UIView *view in cell.subviews) { if ([view isKindOfClass:[UIScrollView class]]) { view.backgroundColor = [UIColor greenColor]; }}

再次編譯并允許應(yīng)用掺逼;綠色高亮確認(rèn)了這個私有類確實(shí)是UIScrollView
的子類吃媒,因?yàn)樗采w了 Cell 里所有的紫色。


回想剛才recursiveDescription
輸出的 log吕喘,UITableViewCellScrollView
的 Frame 和 Cell 本身的 Size 是一致的赘那。
但是,這個視圖到底有什么用兽泄?繼續(xù)拖動 Cell 到左邊漓概,你就會看到 Scroll View 在你拖動 Cell 并 釋放時提供了 “彈性(springy)”行為,如下所示:
swipeable-demo
swipeable-demo

在你創(chuàng)建你自己的自定義UITableViewCell
子類之前病梢,還有一件事要注意胃珍,它出至 UITableViewCell Class Reference:
如果你想超越預(yù)定義樣式,你可以添加子視圖到 Cell 的contentView
上蜓陌。在添加子視圖時觅彰,你自己要負(fù)責(zé)這些視圖的位置以及設(shè)置它們的內(nèi)容。

直白的說钮热,就是填抬,任何對UITableViewCell
的自定義操作只能在contentView
中進(jìn)行。你不能將自己的視圖加在 Cell 下面——而必須將它們加在 Cell 的contentView
上隧期。
這就意味著你將找出你自己的解決方案以便添加自定義按鈕飒责。但不要害怕,你可以很容易地復(fù)制出 Apple 所使用的方案仆潮。
可滑動 Table View Cell 的組成列表
這對你來說是什么意思宏蛉?到了這里,你就有了一個組成列表來制造出一個UITableViewCell
子類性置,以便放上你自定義的按鈕拾并。
我們從 View Stack 的最底部開始列出條目,你的列表如下:
contentView
是你的基礎(chǔ)視圖鹏浅,因?yàn)槟阒荒軐⒆右晥D添加到它上面嗅义。
在用戶滑動后,任何你想顯示的UIButon
隐砸。
一個位于按鈕之上的容器視圖來裝載你所有的內(nèi)容之碗。
你可以使用一個UIScrollView
來作為你的容器視圖,就像 Apple 使用的凰萨,或者使用一個UIPanGestureRecognizer
继控。這同樣能夠處理滑動去顯示/隱藏按鈕械馆。你將在項(xiàng)目中采用后一種方案。
最后武通,一個裝有實(shí)際內(nèi)容的視圖霹崎。

還有一個可能不那么明顯的成分:你必須確保系統(tǒng)提供的UIPanGestureRecognizer
—— 它能讓你滑動顯示 Delete 按鈕 —— 不可用。否則系統(tǒng)手勢會和自定義手勢沖突冶忱。
好消息是設(shè)置默認(rèn)滑動手勢不可用的操作相當(dāng)簡單尾菇。
打開MasterViewController.m
修改tableView:canEditRowAtIndexPath:
永遠(yuǎn)返回NO
,如下所示:

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { return NO;}

編譯并運(yùn)行囚枪;試著滑動某個 Cell 派诬,你會發(fā)現(xiàn)你不能再滑動去刪除了。
為了保持簡單链沼,你將使用兩個按鈕來走完這個教程默赂。但同樣的技術(shù)也可以再一個按鈕上工作,或者超過兩個按鈕的情況——作為提醒括勺,你可能需要執(zhí)行一些本文沒有涉及到的調(diào)整缆八,如果你真的添加了多個按鈕,你必須將整個 Cell 滑出才能看到所有的按鈕疾捍。
創(chuàng)建一個自定義 Cell
你可以從基本視圖和手勢識別列表可以看到奈辰,在 Table View Cell 中有許多要做的事。你將創(chuàng)建一個自定義的UITableViewCell
子類乱豆,以將所有的邏輯放在同一個地方奖恰。
去往File\New\ File…
并選擇iOS\Cocoa Touch\Objective-C class
,將新類命名為SwipeableCell
宛裕,將它設(shè)置為UITableViewCell
的子類 瑟啃,如下所示:


在SwipeableCell.m
中設(shè)置下列類擴(kuò)展和IBOutlet
,就在#import
語句后揩尸,@implementation
語句前:

@interface SwipeableCell()
@property (nonatomic, weak) IBOutlet UIButton *button1;
@property (nonatomic, weak) IBOutlet UIButton *button2;
@property (nonatomic, weak) IBOutlet UIView*myContentView;
@property (nonatomic, weak) IBOutlet UILabel *myTextLabel;@end

下一步翰守,進(jìn)入 Storyboard 選中UITableViewCell
原型,如下所示:


打開 Identity Inspector 疲酌,然后修改 Custom Class 為SwipeableCell
,如下所示:
Change Custom Class
Change Custom Class

現(xiàn)在UITableViewCell
原型的名字在左邊的 Document Outline 上會顯示為 “Swipeable Cell”了袁。右鍵單擊Swipeable Cell – Cell
朗恳,你會看到一個你之前設(shè)置的IBOutlet
列表:
New Name and Outlets
New Name and Outlets

首先,你要在 Attributes Inspector 里修改兩個地方以便自定義視圖载绿。設(shè)置 Style 為Custom
粥诫, Selection 為None
, Accessory 也為None
崭庸,截圖如下:
Reset Cell Items
Reset Cell Items

然后怀浆,拖兩個按鈕到 Cell 的 Content View 里谊囚。在視圖的 Attributes Inspector 區(qū)設(shè)置每個按鈕的背景色為比較鮮艷的顏色,并設(shè)置每個按鈕的文字顏色為比較易讀的顏色执赡,這樣你就可以清楚地看到按鈕镰踏。
將第一個按鈕放在右邊,和contentView
的上下邊緣接觸沙合。將第二個按鈕放在第一個按鈕的左邊緣處奠伪,也和contentView
的上下邊緣接觸。當(dāng)你做好后首懈,Cell 看起來如下绊率,可能顏色少有差異:
Buttons Added to Prototype Cell
Buttons Added to Prototype Cell

接下來,將每個按鈕和對應(yīng)的 Outlet 關(guān)聯(lián)起來究履。右鍵單擊到可滑動Cell上打開它的 Outlets滤否,然后將 button1 拖動到到右邊的按鈕, button2 拖動到左邊的按鈕最仑,如下:
swipeable-button1
swipeable-button1

你需要創(chuàng)建一個方法來處理對每個按鈕的點(diǎn)擊藐俺。
打開SwipeableCell.m
添加如下方法:

- (IBAction)buttonClicked:(id)sender 
{ 
      if (sender == self.button1) 
      { 
      NSLog(@"Clicked button 1!"); 
      } else if (sender == self.button2)
         { 
          NSLog(@"Clicked button 2!");
         } else { NSLog(@"Clicked unknown button!");
     }
}

這個方法處理對兩個按鈕的點(diǎn)擊,通過在控制臺打印記錄盯仪,你就能確定按鈕被點(diǎn)擊了紊搪。
再次打開 Storyboard ,將兩個按鈕都連接上 Action 全景。右鍵單擊Swipeable Cell – Cell
出現(xiàn) Outlet 和 Action 的列表耀石。從buttonClicked:
Action 拖動到你的按鈕,如下:


從事件列表中選擇Touch Up Inside
爸黄,如下所示:
swipeable-touchupinside
swipeable-touchupinside

重復(fù)上述步驟滞伟,用于第二個按鈕。現(xiàn)在隨便按照任何一個按鈕上炕贵,都會調(diào)用buttonClicked:
梆奈。
打開SwipeableCell.m
添加如下屬性:

@property (nonatomic, strong) NSString *itemText;

稍后你將更多的和itemText
打交道,但目前称开,這就是所有你要做的亩钟。
打開MasterViewController.m
并在頂部添加如下一行:

import "SwipeableCell.h"

這將保證這個類知道你自定義的 Cell 子類。
替換tableView:cellForRowAtIndexPath:
的內(nèi)容為:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{
     SwipeableCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath]; 
    NSString *item = _objects[indexPath.row]; cell.itemText = item; return cell;
  }

現(xiàn)在該使用你的新 Cell 而不是標(biāo)準(zhǔn)的UITableViewCell
鳖轰。
編譯并運(yùn)行清酥;你會看到如下界面:


添加一個 Delegate
歐耶~ 你的按鈕已經(jīng)出現(xiàn)了!如果你點(diǎn)擊任何一個按鈕蕴侣,你都會在控制臺看到合適的信息輸出焰轻。然而,你不能指望 Cell 本身去處理任何直接的 Action 昆雀。
比如說辱志,一個 Cell 不能 Present 其他的 View Controller 或直接將其 push 到 Navigation Stack 里蝠筑。你必須要設(shè)置一個 Delegate 來傳遞按鈕的點(diǎn)擊事件回到 View Controller 中去處理那個事件。
打開SwipeableCell.h
并在@interface
之上添加如下 Delegate 協(xié)議:

@protocol SwipeableCellDelegate <NSObject>
- (void)buttonOneActionForItemText:(NSString *)itemText;
- (void)buttonTwoActionForItemText:(NSString *)itemText;
@end

添加如下 Delegate 屬性到SwipeableCell.h
揩懒,就在itemText
屬性下面:

@property (nonatomic, weak) id <SwipeableCellDelegate> delegate;

更新SwipeableCell.m
中的buttonClicked:
為如下所示:

- (IBAction)buttonClicked:(id)sender
 { 
      if (sender == self.button1)
       {
           [self.delegate buttonOneActionForItemText:self.itemText]; 
        } else if (sender == self.button2) 
            { 
                  self.delegate buttonTwoActionForItemText:self.itemText];
             } else { 
                      (@"Clicked unknown button!"); 
                      }
}

這個更新使得這個方法去調(diào)用合適的 Delegate 方法什乙,而不僅僅是打印一句 log。
現(xiàn)在打開MasterViewController.m
并添加如下 delegate 方法:

#pragma mark - SwipeableCellDelegate
- (void)buttonOneActionForItemText:(NSString *)itemText { 
      NSLog(@"In the delegate, Clicked button one for %@", itemText);
    }
- (void)buttonTwoActionForItemText:(NSString *)itemText {
     NSLog(@"In the delegate, Clicked button two for %@", itemText);
    }

這個方法目前還是簡單的打印到控制臺旭从,以確保一切傳遞都工作正常稳强。
接下來,添加如下協(xié)議到MasterViewController.m
頂部的類擴(kuò)展上以符合協(xié)議申明:

@interface MasterViewController () <SwipeableCellDelegate>
 { 
       NSMutableArray *_objects;
 }
@end

這只是簡單地確認(rèn)這個類會實(shí)現(xiàn)SwipeableCellDelegate
協(xié)議和悦。
最后退疫,你要設(shè)置這個 View Controller 為 Cell 的 delegate。
添加如下語句到tableView:cellForRowAtIndexPath:
鸽素,就在最后的 return 語句之前:
cell.delegate = self;

編譯并運(yùn)行褒繁;當(dāng)你點(diǎn)擊按鈕時,你就會看到合適的“In the delegate”消息馍忽。
為按鈕添加 Action
如果你看到log消息很很高興了棒坏,也可以跳過下一節(jié)。然而遭笋,如果你喜歡更加實(shí)在的東西坝冕,你可以添加一些處理,這樣當(dāng) delegate 方法被調(diào)用時瓦呼,你就可以顯示已經(jīng)引入的DetailViewController
喂窟。
添加如下兩個方法到MasterViewController.m

- (void)showDetailWithText:(NSString *)detailText
{ 
      //1 UIStoryboard *storyboard = [UIStoryboard  storyboardWithName:@"Main" bundle:nil]; 
DetailViewController *detail = [storyboard instantiateViewControllerWithIdentifier:@"DetailViewController"]; 
detail.title = @"In the delegate!"; 
detail.detailItem = detailText; 
//2 UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:detail]; 
//3 UIBarButtonItem *done = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(closeModal)];
 [detail.navigationItem setRightBarButtonItem:done]; 
[self presentViewController:navController animated:YES completion:nil];
}
//4
- (void)closeModal
  { 
        [self dismissViewControllerAnimated:YES completion:nil]; 
  }

在上面的代碼里,你執(zhí)行了四個操作:
從 Storyboard 里取出 Detail View Controller 并設(shè)置其 title 和 detailItem 央串。
設(shè)置一個UINavigationController
作為包含 Detail View Controller 的容器磨澡,并給你放置 close 按鈕的地方。
添加 close 按鈕质和,關(guān)聯(lián)MasterViewController
里的一個 Action稳摄。
設(shè)置這個 Action 的響應(yīng)方法,它將 dismiss 任何以 Modal 方式顯示 View Controller

接下來饲宿,用下列版本替換你之前添加的兩個方法:

- (void)buttonOneActionForItemText:(NSString *)itemText
{ 
[self showDetailWithText:[NSString stringWithFormat:@"Clicked button one for %@", itemText]];
}
- (void)buttonTwoActionForItemText:(NSString *)itemText
{
 [self showDetailWithText:[NSString stringWithFormat:@"Clicked button two for %@", itemText]];
}

最后厦酬,打開Main.storyboard
并選中Detail View Controller
。找到 Identity Inspector 并設(shè)置Storyboard ID
為DetailViewController
以匹配類名瘫想,如下所示:


如果你忘了這一步弃锐,instantiateViewControllerWithIdentifier
將會因?yàn)椴缓戏ǖ膮?shù)而 Crash,其異常表示具有這個標(biāo)識符的 View Controller 并不存在殿托。
編譯并運(yùn)行;點(diǎn)擊某個 Cell 中的按鈕剧蚣,然后看著 Modal View Controller 出現(xiàn)支竹,如下面的截圖所示:
View Launched from Delegate
View Launched from Delegate

添加頂層視圖并添加滑動 Action
現(xiàn)在你到了視圖工作的后段部分旋廷,是時候讓頂層部分啟動并運(yùn)行起來了。
打開Main.storyboard
并拖一個UIView
到SwipeableTableCell
上礼搁,這個視圖將占據(jù)整個 Cell 的高和寬饶碘,并覆蓋按鈕,所以在Swipe手勢能工作之前馒吴,你不會再看到它們了扎运。
如果你要精確地控制,打開 Size Inspector 并設(shè)置這個視圖地寬和高饮戳,分別為 320 和 43:
swipeable-320-43
swipeable-320-43

你同樣需要一個約束來將視圖釘在 contentView 的邊緣豪治。選中視圖并點(diǎn)擊Pin
按鈕,選擇所有四個間隔約束并設(shè)置它們的值為 0 扯罐,如下所示:
swipeable-constraint
swipeable-constraint

連接好這個視圖的 Outlet负拟,按照之前介紹的步驟:在左邊的導(dǎo)航器里右鍵單擊這個可滑動 Cell 并拖動myContentView
到這個新的視圖上。
下一步歹河,拖動一個UILabel
到視圖里掩浙;設(shè)置其距離左邊 20 點(diǎn),并設(shè)置其垂直劇中秸歧。再將其連接到myTextLabel
Outlet 上厨姚。
編譯并運(yùn)行;你的 Cell 看起來有正常了:
Back to cells
Back to cells

添加數(shù)據(jù)
但為何實(shí)際的文本數(shù)據(jù)沒有顯示出來键菱?那是因?yàn)槟阒皇窃O(shè)置了itemText
屬性谬墙,而沒有做會影響myTextLabel
的事情。
打開SwipeableCell.m
并添加如下方法:

- (void)setItemText:(NSString *)itemText 
{ 
//Update the instance variable _itemText = itemText; 
//Set the text to the custom label. 
self.myTextLabel.text = _itemText;}

這個方法覆寫了itemText
屬性的 setter 方法纱耻。除了更新后面的實(shí)例變量芭梯,它還會更新可見的 Label。
最后弄喘,為了讓接下來的幾步的結(jié)果更易看到玖喘,你將把 item 的 title 變長一點(diǎn),以便在 Cell 滑動后依然有一些文本可見蘑志。
轉(zhuǎn)到MasterViewController.m
并更新viewDidLoad
中的這一行累奈,這是 item title 生成的地方:

NSString *item = [NSString stringWithFormat:@"Longer Title Item #%d", i];

編譯并運(yùn)行;你就會看到合適的 item title 顯示如下:


手勢識別——GO急但!
終于到了“有趣的”部分——將數(shù)學(xué)澎媒、約束以及手勢識別攪和在一起,以方便地處理滑動操作波桩。
首先戒努,在SwipeableCell
的類擴(kuò)展里添加如下這些屬性:

@property (nonatomic, strong) UIPanGestureRecognizer *panRecognizer;
@property (nonatomic, assign) CGPoint panStartPoint;
@property (nonatomic, assign) CGFloat startingRightLayoutConstraintConstant;
@property (nonatomic, weak) IBOutlet NSLayoutConstraint *contentViewRightConstraint;
@property (nonatomic, weak) IBOutlet NSLayoutConstraint *contentViewLeftConstraint;

關(guān)于你所要做的事情,簡短版本是這樣的:記錄一個 Pan 手勢并調(diào)整你的View的左右約束镐躲,根據(jù) a) 用戶將 Cell Pan 了多遠(yuǎn) b) Cell 在何處以及合適開始移動储玫。
為了做到這一點(diǎn)侍筛,你首先要將這個 IBOutlet 連接到myContentView
的左右約束上。這兩個約束將視圖 釘在 Cell 的contentView
中撒穷。
通過打開約束列表匣椰,你可以找出這兩個約束。通過檢查每個約束在 Cell 上的高亮你就能找到那合適的兩個端礼。在這個例子中禽笑,是contentView
右邊和contentView
之間的約束,如下所示:


一旦你定位到合適的約束蛤奥,就將其連接到合適的 Outlet 上——在本例中佳镜,是contentViewRightConstraint
,如下圖所示:
Hook Up Constraint to IBOutlet
Hook Up Constraint to IBOutlet

遵循同樣的步驟喻括,連接好contentViewLeftConstraint
邀杏,它代表contentView
左邊和contentView
之間的約束。
下一步唬血,打開SwipeableCell.m
并修改@interface
語句的類擴(kuò)展望蜡,添加UIGestureRecognizerDelegate
協(xié)議:

@interface SwipeableCell() <UIGestureRecognizerDelegate>

然后,依然在SwipeableCell.m
里拷恨,添加如下方法:

- (void)awakeFromNib
 {
 [super awakeFromNib]; 
self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panThisCell:)]; 
self.panRecognizer.delegate = self;
 [self.myContentView addGestureRecognizer:self.panRecognizer];
}

這里設(shè)置了 Pan 手勢并將其添加到 Cell 上:
再添加如下方法:

- (void)panThisCell:(UIPanGestureRecognizer *)recognizer 
{ 
switch (recognizer.state)
 {
 case UIGestureRecognizerStateBegan: self.panStartPoint = [recognizer translationInView:self.myContentView]; 
NSLog(@"Pan Began at %@", NSStringFromCGPoint(self.panStartPoint)); 
break; 
case UIGestureRecognizerStateChanged: { CGPoint currentPoint = [recognizer translationInView:self.myContentView]; 
CGFloat deltaX = currentPoint.x - self.panStartPoint.x;
 NSLog(@"Pan Moved %f", deltaX); } 
break; 
case UIGestureRecognizerStateEnded: NSLog(@"Pan Ended"); 
break;
 case UIGestureRecognizerStateCancelled: NSLog(@"Pan Cancelled");
 break; 
default: break;
 }
}

這個方法會在 Pan 手勢識別器發(fā)動時執(zhí)行脖律,暫時,它只簡單地打印 Pan 手勢的細(xì)節(jié)腕侄。
編譯并運(yùn)行小泉;用手指拖動 Cell ,你就會看到如下log記錄了移動信息:


如果你往初始點(diǎn)的右邊滑動冕杠,你會看到正數(shù)微姊,往初始點(diǎn)的左邊滑動就會看到負(fù)數(shù)。這些數(shù)字將用于調(diào)整myContentView
的約束分预。
移動這些約束
從本質(zhì)上將兢交,你需要通過調(diào)整將 Cell 的contentView
釘住的左、右約束來推動myContentView
到左邊笼痹。右約束將會接受一個正值配喳,而左約束將接受一個絕對值相等的負(fù)值。
舉例來說凳干,如果myContentView
需要往左移動 5 點(diǎn)晴裹,那么 右約束將會接受的值是 5,而左約束將接受的值是 -5 救赐。這將會將整個視圖往左邊滑動 5 點(diǎn)涧团,而不會改變他的寬度。
聽起來蠻容易的——但還有許多移動相關(guān)的事情要注意。根據(jù) Cell 是否已經(jīng)打開和用戶 Pan 的方向少欺,你要處理不同的一大把事情喳瓣。
你同樣需要知道 Cell 最遠(yuǎn)可以滑動多遠(yuǎn)。你將通過計(jì)算被按鈕覆蓋的區(qū)域的寬度來確定這一點(diǎn)赞别。最簡單的方法是用視圖的整個寬度減去最左邊的按鈕的最小 X 位置。
為了闡明配乓,下面來個 sneak peek 仿滔,以明確的圖示表明你所要關(guān)注的方面:
Minimum x of button 2
Minimum x of button 2

幸好,感謝 CGRect
CGGeometry 函數(shù) 犹芹,這些很容易被轉(zhuǎn)換為代碼:
添加如下方法到SwipeableCell.m

  • (CGFloat)buttonTotalWidth { return CGRectGetWidth(self.frame) - CGRectGetMinX(self.button2.frame);}

添加如下兩個骨架方法到SwipeableCell.m

- (void)resetConstraintContstantsToZero:(BOOL)animated notifyDelegateDidClose:(BOOL)endEditing
{ 
//TODO: Build.
}
- (void)setConstraintsToShowAllButtons:(BOOL)animated notifyDelegateDidOpen:(BOOL)notifyDelegate
{ 
//TODO: Build
}

這兩個骨架方法——一旦你填上血肉——將 snap 打開 Cell 并 snap 關(guān)閉 Cell崎页。在你對 pan 手勢識別起添加更多處理后,你會回到這兩個方法腰埂。
替換panThisCell:
中的UIGestureRecognizerStateBegan
case 為下列代碼:

case UIGestureRecognizerStateBegan: self.panStartPoint = [recognizer translationInView:self.myContentView]; self.startingRightLayoutConstraintConstant = self.contentViewRightConstraint.constant;
 break;

你需要存儲 Cell 的初始位置(例如飒焦,約束值)以確定 Cell 是要打開還是關(guān)閉。
下一步你需要添加更多處理以應(yīng)對 pan 手勢識別器的改變屿笼。還是在panThisCell:
里牺荠,修改UIGestureRecognizerStateChanged
case ,如下所示:

case UIGestureRecognizerStateChanged: { CGPoint currentPoint = [recognizer translationInView:self.myContentView];
 CGFloat deltaX = currentPoint.x - self.panStartPoint.x; 
BOOL panningLeft = NO; 
if (currentPoint.x < self.panStartPoint.x)
 {
 //1 panningLeft = YES; 
} if (self.startingRightLayoutConstraintConstant == 0) 
{ 
//2 //The cell was closed and is now opening
 if (!panningLeft) 
{ 
CGFloat constant = MAX(-deltaX, 0); 
//3
 if (constant == 0) 
{
 //4 [self resetConstraintContstantsToZero:YES notifyDelegateDidClose:NO]; 
} else { 
//5 self.contentViewRightConstraint.constant = constant;
 } 
} else {
 CGFloat constant = MIN(-deltaX, [self buttonTotalWidth]); 
//6 if (constant == [self buttonTotalWidth]) { 
//7 [self setConstraintsToShowAllButtons:YES notifyDelegateDidOpen:NO];
 } else { 
//8 self.contentViewRightConstraint.constant = constant; 
}
 }
 }

上面大部分代碼都在 Cell 默認(rèn)的“關(guān)閉”狀態(tài)下 處理pan手勢識別器驴一,下面是細(xì)節(jié)說明:
判斷 pan 手勢是往左還是往右休雌。
如果右約束常量為 0 ,意味著myContentView
完全擋住contentView
肝断。因此 Cell 在這里一定已經(jīng)關(guān)閉杈曲,而用戶準(zhǔn)備打開它。
這是處理用戶從做到右滑動以關(guān)閉 Cell 的 情況胸懈。除了說“你不能做那個”之外担扑,你還要處理的情況是,當(dāng)用戶滑動 Cell 只打開一點(diǎn)點(diǎn)趣钱,然后他們希望不必抬起他們的手指來結(jié)束此手勢就可以滑動它關(guān)閉涌献。譯者注:就是說,打開一點(diǎn)點(diǎn)不會完全顯示出后面的按鈕羔挡,Cell 會自動關(guān)閉洁奈。
因?yàn)橐粋€從左到右的滑動會導(dǎo)致deltaX
為正值,而從右到左的滑動回到導(dǎo)致deltaX
為負(fù)值绞灼,你必須根據(jù)負(fù)的deltaX
計(jì)算出常量以設(shè)置到右約束上利术。因?yàn)槭菑乃c0中找出最大值,所以視圖不可能往右邊走多遠(yuǎn)低矮。

如果常量為 0印叁,Cell 就是完全關(guān)閉的。調(diào)用處理關(guān)閉的方法——它(如你回憶起的)在目前還什么也不會做。
如果常量為不為 0轮蜕,那么你就將其設(shè)置到右手邊的約束上昨悼。
否者,如果是從右往做滑動跃洛,那么用戶試圖打開 Cell 率触。這在個情況里,常量將會小于負(fù)deltaX
或兩個按鈕的寬度之和汇竭。
如果目標(biāo)常量是兩個按鈕的寬度之和葱蝗,那么 Cell 就被打開至捕捉點(diǎn)(catch point),你應(yīng)該調(diào)用方法來處理這個打開狀態(tài)细燎。
如果常量不是兩個按鈕的寬度之和两曼,那就將其設(shè)置到右約束上。

喲玻驻!處理得真不少… 而這個只是處理了 Cell 已經(jīng)關(guān)閉得情況悼凑。你現(xiàn)在還要編寫代碼處理當(dāng)手勢開始時 Cell 就已經(jīng)部分開啟的情況。
就在剛在添加的代碼之下添加如下代碼:

else { 
//The cell was at least partially open. CGFloat adjustment = self.startingRightLayoutConstraintConstant - deltaX; 
//1 if (!panningLeft) { CGFloat constant = MAX(adjustment, 0); 
//2 if (constant == 0) { 
//3 [self resetConstraintContstantsToZero:YES notifyDelegateDidClose:NO];
 } else { 
//4 self.contentViewRightConstraint.constant = constant; 
} 
} else { 
CGFloat constant = MIN(adjustment, [self buttonTotalWidth]); 
//5 if (constant == [self buttonTotalWidth]) { 
//6 [self setConstraintsToShowAllButtons:YES notifyDelegateDidOpen:NO]; 
} else {
 //7 self.contentViewRightConstraint.constant = constant; 
}
 }
 } self.contentViewLeftConstraint.constant = -self.contentViewRightConstraint.constant; //8
} break;

這是 if 語句的后半段璧瞬。因此它用于處理 Cell 原本就打開的情況户辫。
再一次,下面說明你要處理的幾個情況:
在這個情況下彪蓬,你只是接受deltaX
寸莫,你就用 rightLayoutConstraint 的原始位置減去deltaX
以便得知要做多少調(diào)整。
如果用戶從做往右滑動档冬,你必須接受 adjustment 與 0 中的較大值膘茎。如果 adjustment 已變成負(fù)值,那就說明用戶已經(jīng)把 Cell 滑到邊界之外了酷誓,Cell 就關(guān)閉了披坏,這就讓你進(jìn)入下一個情況。
如果常量為 0盐数,那么 Cell 已經(jīng)關(guān)閉棒拂,你就調(diào)用處理其關(guān)閉的方法。
否則玫氢,將常量設(shè)置到右約束上帚屉。
對于從右到左的滑動,你將接受 adjustment 與 兩個按鈕寬度之和 中的較小值漾峡。如果 adjustment 更大攻旦,那就表示用戶已經(jīng)滑出超過捕捉點(diǎn)了。
如果常量剛好等于兩個按鈕寬度之和生逸,那么 Cell 就打開了牢屋,你必須調(diào)用處理 Cell 打開的方法且预。
否則,將常量設(shè)置到右約束上烙无。
現(xiàn)在锋谐,你已經(jīng)處理完“Cell關(guān)閉”和“Cell部分開啟”的情況,在這兩個情況里截酷,你都可對左約束做同樣的事情:將其設(shè)置為右約束常量的負(fù)值涮拗。這就保證了myContentView
的寬度一直保持不變。

編譯并運(yùn)行迂苛;現(xiàn)在你可以來回滑動 Cell 多搀!它不是非常流暢,而且它在你希望的地方之前的一點(diǎn)就停下了灾部。這是因?yàn)槟氵€沒有真正實(shí)現(xiàn)那兩個用于處理打開和關(guān)閉 Cell 的方法。
Note:你可以也注意到惯退,Table View 本身已經(jīng)不會 scroll 了赌髓。不要擔(dān)心,一旦你正確處理好 Cell 的滑動催跪,你就能修復(fù)它锁蠕。

Snap!
接下來,你要讓 Cell Snao 進(jìn)入合適的位置懊蒸。你會注意到荣倾,如果你放手 Cell 會停到合適的位置。
在你進(jìn)入方法開始處理之前骑丸,你需要一個單獨(dú)的生成動畫的方法舌仍。
打開SwipeableCell.m
并添加如下方法:
```objc
- (void)updateConstraintsIfNeeded:(BOOL)animated completion:(void (^)(BOOL finished))completion {
 float duration = 0;
 if (animated)
 { 
duration = 0.1; 
} 
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 
[self layoutIfNeeded];
 } completion:completion];
}

Note:0.1 秒的間隔和 ease-out curve 動畫都是我從實(shí)踐和錯誤中總結(jié)出來的。如果你找到其他更讓你看著愉悅的速度或動畫類型通危,可以自由修改它們铸豁。

接下來,你將填充那兩個處理打開和關(guān)閉的骨架方法菊碟。記得在 Apple 的原始實(shí)現(xiàn)里节芥,因?yàn)槭褂昧薝IScrollView
子類作為最底層的試圖,所以會有一點(diǎn)彈性逆害。
要讓事情看起來正確头镊,你將在 Cell 撞到邊界時給它一點(diǎn)彈性空另。你同樣要確保contentView
和myContentView
有同樣的backgroundColor
以造成彈性非常順滑的錯覺孽水。
添加如下常量到SwipeableCell.m
頂部,就在 import 語句之下:
static CGFloat const kBounceValue = 20.0f;

這個常量存儲了彈性值蜒茄,將用于你的彈性動畫中梅垄。
如下更新setConstraintsToShowAllButtons:notifyDelegateDidOpen:

- (void)setConstraintsToShowAllButtons:(BOOL)animated notifyDelegateDidOpen:(BOOL)notifyDelegate
 { 
//TODO: Notify delegate. 
//1 if (self.startingRightLayoutConstraintConstant == [self buttonTotalWidth] && self.contentViewRightConstraint.constant == [self buttonTotalWidth])
 { 
return; 
} 
//2 self.contentViewLeftConstraint.constant = -[self buttonTotalWidth] - kBounceValue; 
self.contentViewRightConstraint.constant = [self buttonTotalWidth] + kBounceValue; 
[self updateConstraintsIfNeeded:animated completion:^(BOOL finished)
 { 
//3 self.contentViewLeftConstraint.constant = -[self buttonTotalWidth]; 
self.contentViewRightConstraint.constant = [self buttonTotalWidth];
 [self updateConstraintsIfNeeded:animated completion:^(BOOL finished) 
{ 
//4 self.startingRightLayoutConstraintConstant = self.contentViewRightConstraint.constant; 
}];
 }];
}

這個方法在 Cell 完全打開時執(zhí)行厂捞。下面解釋發(fā)生了什么:
如果 Cell 已經(jīng)開啟输玷,約束已經(jīng)到達(dá)完全開啟值,那就返回——否則彈性操作將會一次又一次的發(fā)生靡馁,就像你繼續(xù)滑動超過總按鈕寬度那樣欲鹏。
你初始設(shè)置約束值為按鈕總寬度和彈性值的結(jié)合值,它將 Cell 拉到左邊一點(diǎn)點(diǎn)臭墨,這樣才好 snap 回來赔嚎。然后你就調(diào)用動畫來實(shí)現(xiàn)這個設(shè)置。
當(dāng)?shù)谝粋€動畫完成胧弛,發(fā)動第二個動畫尤误,它將 Cell 正好打開在從按鈕寬度的位置。
當(dāng)?shù)诙€動畫完成结缚,重設(shè)起始約束否則你會看到多次彈跳损晤。

如下更新resetConstraintContstantsToZero:notifyDelegateDidClose:

- (void)resetConstraintContstantsToZero:(BOOL)animated notifyDelegateDidClose:(BOOL)notifyDelegate { 
//TODO: Notify delegate. if (self.startingRightLayoutConstraintConstant == 0 && self.contentViewRightConstraint.constant == 0) { 
//Already all the way closed, no bounce necessary return;
 }
 self.contentViewRightConstraint.constant = -kBounceValue; self.contentViewLeftConstraint.constant = kBounceValue; 
[self updateConstraintsIfNeeded:animated completion:^(BOOL finished) { self.contentViewRightConstraint.constant = 0; self.contentViewLeftConstraint.constant = 0;
 [self updateConstraintsIfNeeded:animated completion:^(BOOL finished) { self.startingRightLayoutConstraintConstant = self.contentViewRightConstraint.constant; 
}];
 }];
}

如你所見,這類似于setConstraintsToShowAllButtons:notifyDelegateDidOpen:
红竭,但它的邏輯是關(guān)閉 Cell 而不是打開尤勋。
編譯并運(yùn)行;隨意滑動 Cell 到它的捕捉點(diǎn)茵宪,你就會在放手時看到彈性行為最冰。
然而,如果你在 Cell 完全開啟或完全關(guān)閉之前將釋放手指稀火,它將會卡在中間暖哨。Whoops! 你還沒有處理觸摸結(jié)束或被取消的情況。
找到panThisCell:
用下列代碼替換UIGestureRecognizerStateEnded

case :
case UIGestureRecognizerStateEnded: 
if (self.startingRightLayoutConstraintConstant == 0) { 
//1 //Cell was opening 
CGFloat halfOfButtonOne = CGRectGetWidth(self.button1.frame) / 2; 
//2 
if (self.contentViewRightConstraint.constant >= halfOfButtonOne) { 
//3
 //Open all the way
 [self setConstraintsToShowAllButtons:YES notifyDelegateDidOpen:YES];
 } else {
//Re-close [self resetConstraintContstantsToZero:YES notifyDelegateDidClose:YES]; 
} 
} else { 
//Cell was closing CGFloat buttonOnePlusHalfOfButton2 = CGRectGetWidth(self.button1.frame) + (CGRectGetWidth(self.button2.frame) / 2); 
//4 
if (self.contentViewRightConstraint.constant >= buttonOnePlusHalfOfButton2) { 
//5 //Re-open all the way 
[self setConstraintsToShowAllButtons:YES notifyDelegateDidOpen:YES];
 } else { 
//Close
 [self resetConstraintContstantsToZero:YES notifyDelegateDidClose:YES]; 
} 
} 
break;

在這里凰狞,你根據(jù) Cell 是否已經(jīng)打開或關(guān)閉以及手勢結(jié)束時 Cell 的位置在執(zhí)行不同的處理篇裁。具體來講:
通過檢查開始右約束值,得知手勢開始時 Cell 是否已經(jīng)打開或關(guān)閉服球。
如果 Cell 是關(guān)閉的茴恰,那你就正在打開它,你要讓 Cell 自動滑動到打開斩熊,至少需要先滑動右邊按鈕(self.button1)一半的寬度往枣。因?yàn)槟阍跍y量約束的常量,你只需要計(jì)算實(shí)際的按鈕寬度粉渠,而不是它在視圖中的 X 位置分冈。
接下來,測試約束是否已被打開至超過你希望讓 Cell 自動打開的點(diǎn)霸株。如果已經(jīng)超過雕沉,那就自動打開 Cell。如果沒有去件,那就自動關(guān)閉 Cell坡椒。
此處表示 Cell 從打開的狀態(tài)開始扰路,你需要那個能讓 Cell 自動 snap 關(guān)閉的點(diǎn),至少需要超過最左邊按鈕的一半倔叼。 將不是最左邊的按鈕的那些按鈕的寬度加起來汗唱,在這個情況里,只有 self.button1 而已丈攒,再加上最左邊按鈕的一半——也就是 self.button2 —— 以便找到需要的檢查點(diǎn)哩罪。
測試約束是否以及超過這個點(diǎn),即你希望 Cell 自動關(guān)閉的那個點(diǎn)巡验。如果超過了际插,關(guān)閉 Cell。如果沒有显设,那就重新打開 Cell框弛。

最后,你還要處理一下手勢被取消的情況捕捂。用如下代碼替換UIGestureRecognizerStateCancelled

case :
case UIGestureRecognizerStateCancelled:
 if (self.startingRightLayoutConstraintConstant == 0) {
 //Cell was closed - reset everything to 0 
[self resetConstraintContstantsToZero:YES notifyDelegateDidClose:YES]; 
} else { 
//Cell was open - reset to the open state
 [self setConstraintsToShowAllButtons:YES notifyDelegateDidOpen:YES];
 } 
break;

這個處理相當(dāng)直白功咒;由于用戶取消了觸摸,表示他們不想改變 Cell 當(dāng)前的狀態(tài)绞蹦,所以你只需要將一切都設(shè)置為它們原本的樣子即可。
編譯并運(yùn)行榜旦;滑動 Cell 幽七,你會看到 Cell Snap 到打開或關(guān)閉,而不論你的手指再哪里溅呢,如下所示:


更好地處理 Table View
在最終完成前澡屡,只有少數(shù)幾步了!
首先咐旧,你的UIPanGestureRecognizer
有時候會影響UITableView
的 Scroll 操作驶鹉。由于你已經(jīng)設(shè)置了 Cell 的 Pan 手勢識別器 的UIGestureRecognizerDelegate
,你只需要實(shí)現(xiàn)一個(有些滑稽且冗長命名的) delegate 方法即可將一切恢復(fù)正常铣墨。
添加如下方法到SwipeableCell.m

#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{ 
return YES;
}

這個方法告知各手勢識別器室埋,它們可以同時工作。
編譯并運(yùn)行伊约;打開第一個 Cell 然后你依然可以 Scroll tableView 姚淆。
還有一個 Cell 重用引起的小問題:各個行不記得它們的狀態(tài),看起來是因?yàn)?Cell 重用了它們的視圖的 開啟/關(guān)閉 狀態(tài)屡律,然后它們的視圖就不能正確反應(yīng)用戶的操作了腌逢。要查看這一情況,打開一個 Cell 超埋,然后將 Table Scroll 一點(diǎn)點(diǎn)搏讶。你就會注意每次都有一個 Cell 始終保持打開狀態(tài)佳鳖,但每次都不同。
要修復(fù)這個問題頭一半媒惕,添加如下方法到SwipeableCell.m

- (void)prepareForReuse {
 [super prepareForReuse]; 
[self resetConstraintContstantsToZero:NO notifyDelegateDidClose:NO];
}

這個方法確保 Cell 在其回收重利用時再次關(guān)閉系吩。
要解決這個問題的后一半,你將添加一個公共方法給 Cell 以促使其打開吓笙。然后你會添加一些 delegate 方法以允許MasterViewController
去管理那個 Cell 是打開的淑玫。
打開SwipeableCell.h
。在SwipeableCellDelegate
協(xié)議的申明里面睛,添加如下兩個新的方法絮蒿,就在已存在的那兩個下面:

- (void)cellDidOpen:(UITableViewCell *)cell;
- (void)cellDidClose:(UITableViewCell *)cell;

這些方法將會通知 delegate —— 在你的情況里,就是 Master View Controller —— 某個 Cell 被打開或關(guān)閉了叁鉴。
添加如下公共方法申明到SwipeableCell
的@interface
里:

- (void)openCell;

接下來土涝,打開SwipeableCell.m
并添加openCell
的實(shí)現(xiàn):

- (void)openCell { 
[self setConstraintsToShowAllButtons:NO notifyDelegateDidOpen:NO];
}

這個方法允許 delegate 修改 Cell 的狀態(tài)。
依然在用一個文件里幌墓,找到resetConstraintsToZero:notifyDelegateDidOpen:
并替換其中TODO
為如下代碼:

if (notifyDelegate) {
 [self.delegate cellDidClose:self];
}

接下來但壮,找到setConstraintsToShowAllButtons:notifyDelegateDidClose:
并替換其中TODO
為如下代碼:

if (notifyDelegate) { [self.delegate cellDidOpen:self];}

這兩個修改會在一個 swipe 手勢完成時通知 delegate ,無論 Cell 是否以及打開或關(guān)閉常侣。
添加如下屬性申明到MasterViewController.m
頂部的類擴(kuò)展里:

@property (nonatomic, strong) NSMutableSet *cellsCurrentlyEditing;

它將存儲當(dāng)前已被打開的 Cell 的列表蜡饵。
添加如下代碼到viewDidLoad
的最后:

self.cellsCurrentlyEditing = [NSMutableSet new];

這個初始化保證了之后你可以正常使用數(shù)組。
現(xiàn)在在同一個文件里添加如下方法實(shí)現(xiàn):

- (void)cellDidOpen:(UITableViewCell *)cell {
 NSIndexPath *currentEditingIndexPath = [self.tableView indexPathForCell:cell];
 [self.cellsCurrentlyEditing addObject:currentEditingIndexPath];
}
- (void)cellDidClose:(UITableViewCell *)cell { 
[self.cellsCurrentlyEditing removeObject:[self.tableView indexPathForCell:cell]];
}

注意到你添加的時 Index Path 而不是 Cell 本身到列表里胳施。如果你直接添加 Cell 對象溯祸,那么之后你就會看到同樣的問題,在 Cell 被回收后再次被打開舞肆。用了這個方法焦辅,你就可以使用合適 的 Index Path 來打開 Cell 了。
最后椿胯,添加下面幾行到tableView:cellForRowAtIndexPath:
筷登,就在 return 語句之前:

if ([self.cellsCurrentlyEditing containsObject:indexPath]) { [cell openCell];}

如果當(dāng)前的 Cell 的 Index Path 在列表里,它就會將其設(shè)置為打開哩盲。
編譯并運(yùn)行前方;全都搞定了!你現(xiàn)在有了一個能夠 Scroll 的 Table View廉油,還能處理 Cell 的打開和關(guān)閉狀態(tài)镣丑,并在 Cell 的任意被點(diǎn)擊時,使用 delegate 方法來加載任何任務(wù)娱两。
下一步怎么走莺匠?
譯者注:吐血,終于翻譯到這一句了十兢!
最終的項(xiàng)目可以在此處下載趣竣。我還會繼續(xù)我在此所開發(fā)的東西摇庙,并組成一個開源項(xiàng)目,以便讓事情更有靈活性——在準(zhǔn)備好推出時遥缕,我會在論壇里貼個鏈接卫袒。
任何時候,如你在不知道他們?nèi)绾巫龅降那闆r下復(fù)制出 Apple 所做的某些效果单匣,你都會發(fā)現(xiàn)有許多許多的方式去做到這樣的效果夕凝。所以這里的方案只是這個效果的實(shí)現(xiàn)辦法之一;然而户秤,它是我所發(fā)現(xiàn)的唯一一個不需要處理嵌套 Scroll View 的辦法码秉,產(chǎn)生的手勢識別沖突也可以非常簡單地解決! :]
寫這篇文章時有一些很有用的資源鸡号,但文章里最終使用了非常不同的辦法转砖。這些資源是 Ash Furrow 的文章 能讓一切都工作起來,以及 Massimiliano Bigatti’s BMXSwipeableCell 項(xiàng)目鲸伴,它現(xiàn)實(shí)通過UIScrollView
這條路可以挖到多深府蔗。
如果你有任何建議、問題或相關(guān)的代碼汞窗,請?jiān)谠u論區(qū)講出來吧姓赤!
譯者:@nixzhu
轉(zhuǎn)載自:
https://github.com/nixzhu/dev-blog

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市仲吏,隨后出現(xiàn)的幾起案子模捂,更是在濱河造成了極大的恐慌,老刑警劉巖蜘矢,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異综看,居然都是意外死亡品腹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門红碑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舞吭,“玉大人,你說我怎么就攤上這事析珊∠叟福” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵忠寻,是天一觀的道長惧浴。 經(jīng)常有香客問我,道長奕剃,這世上最難降的妖魔是什么衷旅? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任捐腿,我火速辦了婚禮,結(jié)果婚禮上柿顶,老公的妹妹穿的比我還像新娘茄袖。我一直安慰自己,他們只是感情好嘁锯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布宪祥。 她就那樣靜靜地躺著,像睡著了一般家乘。 火紅的嫁衣襯著肌膚如雪蝗羊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天烤低,我揣著相機(jī)與錄音肘交,去河邊找鬼。 笑死扑馁,一個胖子當(dāng)著我的面吹牛涯呻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腻要,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼复罐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了雄家?” 一聲冷哼從身側(cè)響起效诅,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎趟济,沒想到半個月后乱投,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡顷编,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年戚炫,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片媳纬。...
    茶點(diǎn)故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡双肤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钮惠,到底是詐尸還是另有隱情茅糜,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布素挽,位于F島的核電站蔑赘,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜米死,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一锌历、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧峦筒,春花似錦究西、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至峦失,卻和暖如春扇丛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尉辑。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工帆精, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人隧魄。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓卓练,卻偏偏與公主長得像,于是被迫代替她去往敵國和親购啄。 傳聞我的和親對象是個殘疾皇子襟企,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 本文翻譯自 http://www.raywenderlich.com/62435/make-swipeable-t...
    Obsession丶執(zhí)閱讀 2,433評論 0 3
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件狮含、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,103評論 4 62
  • 介紹: 使用Tensorflow做線性回歸 codes: References [1] TensorFlow入門一
    BillLeee閱讀 1,043評論 0 1
  • 暮春四月几迄,和孩子們一起做了戚風(fēng)蛋糕蔚龙。陽光透過窗戶灑在窗臺上,耳邊縈繞著孩子們嘰嘰喳喳的細(xì)碎聲音映胁,教室的小黑板上寫著...
    蘅丁閱讀 344評論 0 1
  • 小r吃飯的時候問:媽媽木羹,你小的時候你的爸爸媽媽有沒有考慮過你的未來? 我答:沒有哪個父母不希望孩子有個好的未來屿愚。 ...
    小r念經(jīng)閱讀 197評論 0 1