iOS中設計一個Block代碼執行的UIAlertView


Windows下的AlertView(好比java的MessageBox, JS的alert('title'))都是阻塞代碼繼續執行的,舉個例子html

int result = window.confirm('你會點擊取消按鈕麼?');
console.log("If I havn't confirm, I won't be logged."); 
if (result == 1) { 
// some code 
}
java

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
[STAlertView.m]
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

相關文章
相關標籤/搜索