将UIActionSheet选项连接到操作的正确方法

时间:2010-10-20 03:04:13

标签: iphone objective-c design-patterns uiactionsheet

在iphone应用程序中使用UIActionSheet时,将操作与按钮匹配的典型方法看起来非常脆弱且在美学上令人不愉快。也许是由于我最小的C / C ++背景(更多Perl,Java,Lisp等)。对按钮索引进行匹配似乎太多魔术数字而且断开连接以避免简单的逻辑或一致性错误。

例如,

UIActionSheet *sources = [[UIActionSheet alloc]
         initWithTitle:@"Social Networks"
              delegate:self 
     cancelButtonTitle:@"Cancel" 
destructiveButtonTitle:nil 
     otherButtonTitles:@"Twitter", @"Facebook", @"Myspace", @"LinkedIn", @"BlahBlah", nil
];

<snip>

-(void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
    if (buttonIndex == [actionSheet cancelButtonIndex]) {
        // all done
    } else if (buttonIndex == 0) {
        // Twitter
    } else if (buttonIndex == 1) {
        // Facebook
    } else if (buttonIndex == 2) {
        // LinkedIn
    } else if (buttonIndex == 3) {
        // Myspace
    }
}

请注意,操作处理代码中至少有两个错误(至少根据评论)。

我缺少的是在Objective-C中避免断开连接的正确设计模式。如果这是perl,我将首先构建一个我的按钮选项数组,然后可能创建一个快速查找表哈希,它对应于另一个对象或子例程的查找表,它们为每个项目做了适当的事情。在java中,原始列表可能首先是具有回调的对象。我知道我可以构建一个字典来模仿perl哈希,但这对于3-4个选项来说感觉非常笨拙和麻烦。我还考虑使用枚举来掩盖索引的魔力,但这只是问题的一小部分。

真正的问题似乎是没有(简单?)方法在一个地方指定按钮字符串列表和相应的操作,因此在添加/删除/重新排序选项时无需修改两个地方的代码使得我的示例代码犯下的错误实际上是不可能的。

我不是想尝试编写一种编程语言圣战,我只是想在这个场景中找出正确的设计模式(我相信在Objective C中有很多其他的)来将按钮字符串列表连接到列表动作。

5 个答案:

答案 0 :(得分:4)

我更喜欢这种方式

- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
    if (buttonIndex == [actionSheet cancelButtonIndex]) 
    {
       // cancelled, nothing happen
       return;
    }

    // obtain a human-readable option string
    NSString *option = [actionSheet buttonTitleAtIndex:buttonIndex];
    if ([option isEqualToString:@"Twitter"])
    {
        //...
    } else if ([option isEqualToString:@"FaceBook"])
    {
        //...
    }
}

答案 1 :(得分:3)

我完全同意这个问题。苹果公司的设计鼓励使用魔术数字,看到所有解决方案推荐使用硬编码按钮索引,我感到有点震惊。

这是我对Swift的解决方案。

  • 为每个按钮标题创建一个包含项目的枚举,例如:
enum ImagePickerActionSheetButtons
{
    case Camera
    case Chooser
}

使用每个按钮标题的LOCALIZED字符串填充字典,其中键是枚举中的项目:

// Populate with LOCALIZED STRINGS
var buttonTitles:[ImagePickerActionSheetButtons:String] =
[ImagePickerActionSheetButtons.Camera:"Take photo",
    ImagePickerActionSheetButtons.Chooser :"Choose photo"]

创建操作表,从字典值中获取字典中的按钮标题:

func createActionSheet()->UIActionSheet
{
    var sheet: UIActionSheet = UIActionSheet()

    sheet.addButtonWithTitle(buttonTitles[.Camera]!)
    sheet.addButtonWithTitle(buttonTitles[.Chooser]!)

    sheet.addButtonWithTitle("Cancel")
    sheet.cancelButtonIndex = sheet.numberOfButtons - 1
    sheet.delegate = self
    return sheet
}

最后,在clickedButtonAtIndex代码中,针对字典中的本地化字符串检查单击按钮的标题:

func actionSheet(sheet: UIActionSheet!, clickedButtonAtIndex buttonIndex: Int)
{
    if (sheet.buttonTitleAtIndex(buttonIndex) == buttonTitles[.Camera]!)
    {
        takePhoto()
    }
    else if (sheet.buttonTitleAtIndex(buttonIndex) == buttonTitles[.Chooser]!)
    {
        choosePicFromLibrary()
    }
    else if (buttonIndex == sheet.cancelButtonIndex)
    {
        // do nothing
    }
}

答案 2 :(得分:1)

也许您可以将按钮的操作放在数组中

actionsArray = [NSMutableArray arrayWithObjects: @selector(btn1Clicked),    
                                    @selector(btn2Clicked), 
                                    @selector(btn3Clicked), 
                                    @selector(btn4Clicked), nil];

然后在didDismissWthButtonIndex

-(void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
    if (buttonIndex == [actionSheet cancelButtonIndex]) {
        // all done
    } else {
       [this [actionsArray objectAtIndex: buttonIndex]];
    }
}

我很确定你可以在数组中放入一个更复杂的对象,包括按钮信息和方法,然后将它全部包含在数组中。可能更好的错误检查数组的索引....等

老实说,在我阅读这个问题之前,我从未想过这个模式,所以这只是我的头脑。

答案 3 :(得分:1)

那个怎么样?

这样,您就不必担心索引,因为按钮和操作都添加在同一个地方。

typedef void (^contact_callback_t)(MyContactsController *controller);
 … 
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
     NSDictionary *contact = [myContacts objectAtIndex:indexPath.row];     
     UIActionSheet *_actionSheet = [[UIActionSheet alloc] initWithTitle:NSLocalizedString(@"Contact Action", @"")
                                                          delegate:self
                                                 cancelButtonTitle:nil
                                            destructiveButtonTitle:nil
                                                 otherButtonTitles:nil];

 _actions = [NSMutableArray new];
 if([contact objectForKey:@"private_email"] != nil) {
     [_actionSheet addButtonWithTitle:
      [NSString stringWithFormat:NSLocalizedString(@"E-Mail: %@", @""), [contact objectForKey:@"private_email"] ] ];
     contact_callback_t callback = ^(MyContactsController *controller) {
         [controller openEmail:contact];
     };
     [_actions addObject:callback];
 }
 if([contact objectForKey:@"private_telefon"] != nil) {
     [_actionSheet addButtonWithTitle: 
      [NSString stringWithFormat:NSLocalizedString(@"Phone: %@", @""), [contact objectForKey:@"private_telefon"] ]];
     contact_callback_t callback = ^(MyContactsController *controller) {
         [controller dial:[contact objectForKey:@"private_telefon"]];
     };
     [_actions addObject:callback];
   }
  [_actionSheet showFromTabBar:tabBar];     

}

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex 
{
  if(buttonIndex == actionSheet.cancelButtonIndex)
{
}
else
{
      contact_callback_t callback = [_actions objectAtIndex:buttonIndex];
      callback(self);
   }
  _actions = nil;
}

答案 4 :(得分:-1)

基于Aaron的选择器建议,我现在非常喜欢做一个简单的ad-hoc调度方法。它成功地避免了处理错误选项的可能性以及提供关注的清晰因子分解。当然,我可以想象一个用例,你想先为每个选项做一些其他事情,比如实例化一个对象并将其传递给选项字符串,就像Toro的回答一样。

这是一个简单的调度,调用诸如'actionTwitter'之类的方法:

-(void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {  
    if (buttonIndex == [actionSheet cancelButtonIndex]) {
        return;
    }

    NSString *methodName = [@"action" stringByAppendingString:[actionSheet buttonTitleAtIndex:buttonIndex]];
    SEL actionMethod = NSSelectorFromString(methodName);
    if ([self respondsToSelector:actionMethod]) {
        [self performSelector:actionMethod];
    } else {
        NSLog(@"Not yet implemented")
    }
}