封装同步的UIActionSheet

来源:互联网 发布:西咸新区大数据交易所 编辑:程序博客网 时间:2024/04/30 15:24

封装同步的UIActionSheet

Jun 24th, 2012

问题

做iOS开发的同学想必都用过UIActionSheet。UIActionSheet可以弹出一个选择列表,让用户选择列表中的某一项操作。使用UIActionSheet非常简单,以下是一个简单的示例代码:

12345678910
- (void)someButtonClicked {    UIActionSheet * sheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:@"ddd" destructiveButtonTitle:@"aaa" otherButtonTitles:@"bbb", @"ccc", @"ddd", nil];    sheet.destructiveButtonIndex = 1;    [sheet showInView:self.view];}- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {    int result = buttonIndex;    NSLog(@"result = %d", result);}

但我个人在使用时,感觉UIActionSheet有以下2个问题:

  1. UIActionSheet是一个异步的调用,需要设置delegate来获得用户选择的结果。这么小粒度的选择界面,把调用显示和回调方法分开写在2个方法中,使得原本简单的逻辑复杂了。虽然也不会复杂到哪儿去,但是每次调用UIActionSheet就需要另外写一个delegate回调方法,让我觉得这是一个过度的设计。如果UIActionSheet在弹出界面时,是一个同步调用,在调用完 showInView方法后,就能获得用户的点击结果,那该多方便。

  2. UIActionSheet默认的init方法比较恶心。cancel Button其实默认是在最底部的,但是在init方法中是放在第一个参数。destructive默认是列表的第一个。如果你需要的界面不是将destructive button放在第一个,就需要再指定一次destructiveButtonIndex,而这个index的下标,是忽略cancel button来数的,虽说也不是很麻烦,但是心里感觉比较恶心。

改造UIActionSheet

基于上面2个原因,我想把UIActionSheet改造成一个同步的调用。这样,在我调用它的 showInView方法后,我希望它直接同步地返回用户的选择项,而不是通过一个Delegate方法来回调我。另外,我也不希望init方法有那么多麻烦的参数,我只希望init的时候,指定一个数组能够设置每个button的title就行了。

于是我写了一个 SynchronizedUIActionSheet 类,这个类将 UIActionSheet简单封装了一下,利用 CFRunLoopRun 和 CFRunLoopStop 方法来将UIActionSheet改造成同步的调用。整个代码如下所示:

SynchronizedUIActionSheet.h 文件:

123456789101112131415
// SynchronizedUIActionSheet.h#import <Foundation/Foundation.h>@interface SynchronizedUIActionSheet : NSObject<UIActionSheetDelegate>@property (nonatomic, strong) NSArray * titles;@property (nonatomic, assign) NSInteger destructiveButtonIndex;@property (nonatomic, assign) NSInteger cancelButtonIndex;- (id)initWithTitles:(NSArray *)titles;- (NSInteger)showInView:(UIView *)view;@end

SynchronizedUIActionSheet.m 文件:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
#import "SynchronizedUIActionSheet.h"@implementation SynchronizedUIActionSheet {    UIActionSheet * _actionSheet;    NSInteger _selectedIndex;}@synthesize titles = _titles;@synthesize destructiveButtonIndex = _destructiveButtonIndex;@synthesize cancelButtonIndex = _cancelButtonIndex;- (id)initWithTitles:(NSArray *)titles {    self = [super init];    if (self) {        _titles = titles;        _destructiveButtonIndex = 0;        _cancelButtonIndex = titles.count - 1;    }    return self;}- (void)setTitles:(NSArray *)titles {    _titles = titles;    _cancelButtonIndex = titles.count - 1;}- (NSInteger)showInView:(UIView *)view {    _actionSheet = [[UIActionSheet alloc] init];    for (NSString * title in _titles) {        [_actionSheet addButtonWithTitle:title];    }    if (_destructiveButtonIndex != -1) {        _actionSheet.destructiveButtonIndex = _destructiveButtonIndex;    }    if (_cancelButtonIndex != -1) {        _actionSheet.cancelButtonIndex = _cancelButtonIndex;    }    [_actionSheet showInView:view];    CFRunLoopRun();    return _selectedIndex;}- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {    _selectedIndex = buttonIndex;    _actionSheet = nil;    CFRunLoopStop(CFRunLoopGetCurrent());}@end

在改造后,调用ActionSheet的示例代码如下,是不是感觉逻辑清爽了一些?

1234567
- (IBAction)testButtonPressed:(id)sender {    SynchronizedUIActionSheet * synActionSheet = [[SynchronizedUIActionSheet alloc] init];    synActionSheet.titles = [NSArray arrayWithObjects:@"aaa", @"bbb", @"ccc", @"ddd", nil];    synActionSheet.destructiveButtonIndex = 1;    NSUInteger result = [synActionSheet showInView:self.view];    NSLog(@"result = %d", result);}

总结

利用NSRunLoop来将原本的异步方法改成同步,可以使我们在某些情形下,方便地将异步方法变成同步方法来执行。

例如以前我们在做有道云笔记iPad版的时候,采用的图片多选控件需要用户允许我们获得地理位置信息,如果用户没有选择允许,那个这个图片多选控件就会执行失败。为了不让这个控件挂掉,我们想在用户禁止访问地理位置时,不使用该控件,而使用系统自带的图片单选的UIImagePickerController 控件来选择图片。对于这个需求,我们明显就希望将获得地理位置信息这个系统确认框做成同步的,使得我们可以根据用户的选择再决定用哪种图片选择方式。最终,我们也用类似上面的方法,用NSRunLoop来使我们的异步方法调用暂停在某一行,直到获得用户的反馈后,再往下执行,示例代码如下:

12345678
- (id)someCheck {    BOOL isOver = NO;    // do the async check method, after the method return, set isOver to YES    while (!isOver) {        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];    }    return value;}

以上Demo代码我放到github上了,地址是这里,请随意取用。祝端午节快乐。

0 0
原创粉丝点击