Windows下的AlertView(好比java的MessageBox, JS的alert('title'))都是阻塞代碼繼續執行的,舉個例子html
int result = window.confirm('你會點擊取消按鈕麼?');
java
console.log("If I havn't confirm, I won't be logged.");
if (result == 1) {
// some code
}
iOS原生提供的UIAlertView就不能實現相似的效果,可是依舊能夠本身Custom實現,本篇博客將介紹如何在iOS下實現相似windows這樣的彈出框,若是不點擊確認按鈕則不執行後續的代碼。ios
先簡單介紹一下iOS中的UIAlertView:UIAlertView是以回調的形式通知調用方,調用完show方法以後,後續的代碼能夠繼續執行,等AlertView處理完以後,會異步通知掉用方我剛纔進行了什麼操做。git
在開始這篇文章以前,你須要準備一些知識(NSRunloop),不少博客有專門的介紹,爲了不重複的博客,在這裏不作詳細的介紹。若是你尚未了解,那麼你得先去了解,能夠直接Google NSRunloop 或者直接看官方文檔 Threading Programming Guide: Run Loops,或者若是你不想了解Runloop,若是你有需求,你能夠直接下載最後完成的代碼。github
準備好了知識以後,咱們來考慮一下如何設計咱們的AlertView,命名暫定爲STAlertView。咱們大概要定義一個方法,windows
- (NSInteger)showInView:(UIView *)view animated:(BOOL)animated;
app
咱們考慮下如何才能讓代碼不能繼續執行,可是咱們可讓應用接受全部的用戶事件呢?異步
先補充點知識:iOS的程序運行是事件驅動的,這點本質上和Windows的消息驅動是同樣的。意思就是說,iOS維護者一個事件隊列,主線程不斷的從事件隊列中去取事件,而後回調給咱們的程序去處理,好比咱們按鈕的按下效果,點擊事件,列表滾動等等,固然事件不止包含點擊事件,這裏就簡單說明下。ide
再補充一點知識:你們能夠在上面給出的蘋果文檔中看到,Runloop 提供一個run方法,這個方法可使Runloop 處於某種狀態,直到時間到達指定的limitDate,其實這些也就是Runloop能在閒事處於低耗的緣由。沒有事件,就當前線程處於休眠狀態,若是有事件就執行事件,這就是Runloop可以減小CPU空轉的緣由。oop
在正式開始以前,咱們先來看一個例子,例子很簡單,只有一個while(true)循環,簡單代碼以下:
[TestRunloop]123456789101112131415161718192021222324 |
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. UIButton *button1 = [UIButton buttonWithType:UIButtonTypeCustom]; button1.frame = CGRectMake(100, 100, 100, 30); [button1 setTitle:@"測試按鈕" forState:UIControlStateNormal]; [button1 setTitleColor:[UIColor blueColor] forState:UIControlStateNormal]; [button1 setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted]; [button1 addTarget:self action:@selector(testButtonActionFired:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:button1];}- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; NSLog(@"Did Start viewDidAppear"); while (!_shouldContinue) { [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } NSLog(@"Will End viewDidAppear");}- (void)testButtonActionFired:(UIButton *)button { NSLog(@"TestButtonActionFired");} |
代碼ViewDidLoad中建立了一個標題爲測試按鈕的Button,爲其設置了按下和正常的文本顏色,添加了一個點擊事件。ViewDidAppwar中(_shouldContinue爲一個全局BOOL變量,值爲false),寫了一個while(true)循環,裏面的內容就是讓Runloop處於DefaultMode
代碼的執行效果咱們能夠先預測一下(咱們預測打印的值)
這一句確定會被執行 NSLog(@"Did Start viewDidAppear");若是沒有執行,說明你的這個view沒有被添加到window上,
這一句確定不會被執行 NSLog(@"Will End viewDidAppear"); 因爲上面有while(true),至關於死循環,因此以後的內容確定不被執行到,若是對以上兩點有異議的,建議能夠先了解一下基礎。
那麼問題來了 ?
按鈕的事件會不會被執行到,按鈕的點擊效果會不會實現?
在博客前面說過一些關於Runloop的,你們能夠拿上面的例子驗證一下(觸摸事件算一種事件源),處於Default / UITrackingMode的Runloop是能夠接受用戶觸摸事件的。因此答案揭曉,按鈕的點擊動做能夠執行,點擊效果能夠顯示,咱們看一下點擊三次測試按鈕的打印的日誌,後續會附帶源碼下載地址
[打印日誌]1234 |
2014-10-27 15:29:36.796 STRunloop[3474:195616] Did Start viewDidAppear2014-10-27 15:29:39.493 STRunloop[3474:195616] TestButtonActionFired2014-10-27 15:29:40.487 STRunloop[3474:195616] TestButtonActionFired2014-10-27 15:29:40.902 STRunloop[3474:195616] TestButtonActionFired |
代碼如預期執行,沒有錯的,這樣看來,咱們如今須要作的就是保證事件機制的正常運行,以及block住當前代碼就能夠了,根據上面的知識,咱們能夠獲得一個方法原型
[ShowInView]1234567 |
- (NSInteger)showInView:(UIView *)view animated:(BOOL)animated { // while (!_shouldContinue) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } return _dismissedIndex;} |
咱們只須要在用戶點擊完完成按鈕以後,爲_dismissedIndex和_shouldContinue賦值就能夠了。到這裏以後,咱們差很少就已經實現了一個Block執行的AlertView,剩餘的代碼都不復雜,我就直接貼代碼了
[STAlertView.h]1234567891011121314151617 |
//// STAlertView.h// STKitDemo//// Created by SunJiangting on 14-8-28.// Copyright (c) 2014年 SunJiangting. All rights reserved.//#import <UIKit/UIKit.h>@interface STAlertView : UIView- (instancetype)initWithMenuTitles:(NSString *)menuTitle, ... NS_REQUIRES_NIL_TERMINATION;- (NSInteger)showInView:(UIView *)view animated:(BOOL)animated;@end |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
//// STAlertView.m// STKitDemo//// Created by SunJiangting on 14-8-28.// Copyright (c) 2014年 SunJiangting. All rights reserved.//#import "STAlertView.h"#import <STKit/STKit.h>@interface _STAlertViewCell : UITableViewCell@property(nonatomic, strong) UILabel *titleLabel;@property(nonatomic, strong) UIView *separatorView;@endconst CGFloat _STAlertViewCellHeight = 45;@implementation _STAlertViewCell- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if (self) { self.frame = CGRectMake(0, 0, 320, _STAlertViewCellHeight); self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, 300, 44)]; self.titleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.titleLabel.textColor = [UIColor darkGrayColor]; self.titleLabel.highlightedTextColor = [UIColor whiteColor]; self.titleLabel.font = [UIFont systemFontOfSize:17.]; self.titleLabel.textAlignment = NSTextAlignmentCenter; [self addSubview:self.titleLabel]; self.separatorView = [[UIView alloc] initWithFrame:CGRectZero]; [self addSubview:self.separatorView]; } return self;}- (void)setFrame:(CGRect)frame { [super setFrame:frame]; self.separatorView.frame = CGRectMake(0, CGRectGetHeight(frame) - STOnePixel(), CGRectGetWidth(frame), STOnePixel());}- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated { [super setHighlighted:highlighted animated:animated]; self.separatorView.backgroundColor = [UIColor colorWithRed:0x99/255. green:0x99/255. blue:0x99/255. alpha:1.0];}@end@interface STAlertView () <UITableViewDataSource, UITableViewDelegate, UIGestureRecognizerDelegate> { BOOL _shouldContinue; NSInteger _dismissedIndex;}@property(nonatomic, strong) NSMutableArray *dataSource;@property(nonatomic, weak) UITableView *tableView;@property(nonatomic, weak) UIView *backgroundView;@property(nonatomic, weak) UIView *contentView;@end@implementation STAlertView- (instancetype)initWithMenuTitles:(NSString *)menuTitle, ... NS_REQUIRES_NIL_TERMINATION { NSMutableArray *dataSource = [NSMutableArray arrayWithCapacity:1]; NSString *title = nil; va_list args; if (menuTitle) { [dataSource addObject:menuTitle]; va_start(args, menuTitle); while ((title = va_arg(args, NSString *))) { [dataSource addObject:title]; } va_end(args); } self = [super initWithFrame:CGRectZero]; if (self) { self.dataSource = dataSource; CGFloat maxHeight = 240; const CGFloat footerHeight = 1; CGFloat height = dataSource.count * _STAlertViewCellHeight + footerHeight; UIView *backgroundView = [[UIView alloc] initWithFrame:self.bounds]; backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; backgroundView.backgroundColor = [UIColor blackColor]; [self addSubview:backgroundView]; self.backgroundView = backgroundView; UIView *contentView = [[UIView alloc] initWithFrame:CGRectMake(0, STOnePixel(), 0, MIN(height, maxHeight))]; contentView.clipsToBounds = YES; [self addSubview:contentView]; self.contentView = contentView; UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissActionFired:)]; tapGesture.numberOfTapsRequired = 1; tapGesture.delegate = self; [self.backgroundView addGestureRecognizer:tapGesture]; UITableView *tableView = [[UITableView alloc] initWithFrame:self.contentView.bounds style:UITableViewStylePlain]; tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; tableView.delegate = self; tableView.dataSource = self; tableView.backgroundView = nil; tableView.separatorStyle = UITableViewCellSeparatorStyleNone; [tableView registerClass:[_STAlertViewCell class] forCellReuseIdentifier:@"Identifier"]; [self.contentView addSubview:tableView]; self.tableView = tableView; tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, footerHeight)]; tableView.tableFooterView.backgroundColor = [UIColor colorWithRed:0xFF/255. green:0x73/255. blue:0 alpha:1.0]; if (height > maxHeight) { tableView.scrollEnabled = YES; } else { tableView.scrollEnabled = NO; } } return self;}- (NSInteger)showInView:(UIView *)view animated:(BOOL)animated { // { self.tableView.contentInset = UIEdgeInsetsZero; if (!view) { view = [UIApplication sharedApplication].keyWindow; [view addSubview:self]; } else { self.frame = view.bounds; [view addSubview:self]; } CGRect frame = self.contentView.frame; frame.size.width = CGRectGetWidth(view.bounds); self.backgroundView.alpha = 0.0; CGRect fromRect = frame, targetRect = frame; fromRect.size.height = 0; self.contentView.frame = fromRect; void (^animation)(void) = ^(void) { self.backgroundView.alpha = 0.5; self.contentView.frame = targetRect; }; void (^completion)(BOOL) = ^(BOOL finished) { }; if (animated) { [UIView animateWithDuration:0.35 animations:animation completion:completion]; } } while (!_shouldContinue) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } return _dismissedIndex;}- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1;}- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { return 0;}- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { return 0;}- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.dataSource.count;}- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return _STAlertViewCellHeight;}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { _STAlertViewCell *tableViewCell = [tableView dequeueReusableCellWithIdentifier:@"Identifier"]; tableViewCell.titleLabel.text = self.dataSource[indexPath.row]; return tableViewCell;}- (void)dismissAnimated:(BOOL)animated { [self _dismissAnimated:animated completion:^(BOOL finished){}];}#pragma mark - PrivateMethod- (void)dismissActionFired:(UITapGestureRecognizer *)sender { _dismissedIndex = -1; [self _dismissAnimated:YES completion:^(BOOL finished){}];}- (void)_dismissAnimated:(BOOL)animated completion:(void (^)(BOOL))_completion { self.backgroundView.alpha = 0.5; CGRect fromRect = self.contentView.frame, targetRect = self.contentView.frame; self.contentView.frame = fromRect; targetRect.size.height = 0; void (^animation)(void) = ^(void) { self.backgroundView.alpha = 0.0; self.contentView.frame = targetRect; }; void (^completion)(BOOL) = ^(BOOL finished) { _shouldContinue = YES; if (_completion) { _completion(finished); } [self removeFromSuperview]; }; if (animated) { [UIView animateWithDuration:0.35 animations:animation completion:completion]; } else { animation(); completion(YES); }}- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:YES]; _dismissedIndex = indexPath.row; [self dismissAnimated:YES];}- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { CGPoint touchedPoint = [gestureRecognizer locationInView:self]; if (touchedPoint.y < CGRectGetMaxY(self.contentView.frame) && touchedPoint.y > CGRectGetMinX(self.contentView.frame)) { return NO; } return YES;}@end |
說明一下: STOnePixel() 表明1px,因爲在其餘共有類裏面申明的,就不把其餘類粘在這裏了,方法實現
CGFloat STOnePixel() { return 1.0 / [UIScreen mainScreen].scale; }
PS: 這只是一種技能的get,不表明必須這麼作,大部分狀況我仍是使用異步+Callback的形式來處理相似的問題的。
你們能夠在個人github上下載代碼的 Demo