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

发布于 2024-09-28 05:12:19 字数 1323 浏览 5 评论 0原文

在 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 中的许多其他模式)将按钮字符串列表连接到操作列表的正确设计模式是什么。

While using a UIActionSheet in an iphone app, the typical methods of matching actions to buttons seem very fragile and aesthetically unpleasant. Perhaps its due to my minimal C/C++ background (more Perl, Java, Lisp and others). Matching on button indexes just seems like too many magic numbers and too disconnected to avoid simple logical or consistency errors.

For instance,

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
    }
}

Notice there's at least two errors in the action handling code (according to the comments at least).

What I'm missing is the correct design pattern for avoiding that disconnect in Objective-C. If this were perl, I would first build an array of my button options and then probably create a quick lookup table hash that would correspond to another lookup table of objects or subroutines that did the appropriate thing for each item. In java, the original list would probably be objects in the first place with callbacks. I know I could build a Dictionary to mimic the perl hash but that feels very unwieldy and cumbersome for 3-4 options. I've also considered using a enum to mask the magic-ness of indexes but that's only a minor part of the problem.

The real problem seems to be that there's no (simple?) way to specify BOTH the list of button strings and corresponding actions in one place thereby eliminating the need to modify code in two places when adding/removing/reordering options and thus making it effectively impossible to make the kinds of mistakes that my sample code makes.

I'm not trying to start a programming language holy war, I just want to figure what the correct design pattern in this scenario (and I believe many others in Objective C) for connecting the list of button strings to the list of actions.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

最后的乘客 2024-10-05 05:12:19

我更喜欢这种方式

- (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"])
    {
        //...
    }
}

I prefer this way

- (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"])
    {
        //...
    }
}
情何以堪。 2024-10-05 05:12:19

我完全同意这个问题。 Apple 的设计鼓励使用幻数,当我看到所有建议对按钮索引使用硬编码数字的解决方案时,我感到有点震惊。

这是我的 Swift 解决方案。

  • 创建一个包含每个按钮标题的项目的枚举,例如:
枚举 ImagePickerActionSheetButtons
{
    案例 相机
    案例选择器
}

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

// 使用本地化字符串填充
var ButtonTitles:[ImagePickerActionSheetButtons:String] =
[ImagePickerActionSheetButtons.Camera:"拍照",
    ImagePickerActionSheetButtons.Chooser :"选择照片"]

创建操作表,通过枚举值从字典中获取按钮标题:

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

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

    sheet.addButtonWithTitle("取消")
    sheet.cancelButtonIndex =sheet.numberOfButtons - 1
    Sheet.delegate = self
    退货单
}

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

func actionSheet(sheet: UIActionSheet!, clickedButtonAtIndex buttonIndex: Int)
{
    if (sheet.buttonTitleAtIndex(buttonIndex) == buttonTitles[.Camera]!)
    {
        拍照()
    }
    else if (sheet.buttonTitleAtIndex(buttonIndex) == buttonTitles[.Chooser]!)
    {
        从库中选择图片()
    }
    else if (buttonIndex ==sheet.cancelButtonIndex)
    {
        // 什么都不做
    }
}

I completely agree with the question. Apple's design here encourages the use of magic numbers, and I was a little shocked to see all the solutions out there recommending use of hardcoded numbers for the button indexes.

Here's my solution for Swift.

  • Create an enum containing an item for every button title, like:
enum ImagePickerActionSheetButtons
{
    case Camera
    case Chooser
}

Populate a dictionary with the LOCALIZED strings for each button title, where the keys are items from the enum:

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

Create the action sheet, getting the button titles from the dictionary by their enum value:

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
}

Finally, in the clickedButtonAtIndex code, check the title of the clicked button against the localized strings in the dictionary:

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
    }
}
心房敞 2024-10-05 05:12:19

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

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]];
    }
}

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

说实话,在我读到这个问题之前,我从来没有想过这个模式,所以这只是我的想法

Maybe you could put the actions for the buttons in an array

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

then in didDismissWthButtonIndex

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

I am pretty certain you could put a more complex object in the array including button info and method and then have it all contained in the array. Probably better error checking on the index of the array....etc

To be honest with you I never thought about this pattern much until i read the question so this is just off the top of my head

囍孤女 2024-10-05 05:12:19

那个怎么样?

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

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;
}

How about that one?

In that way, you don't have to worry about indexes since buttons and actions are added in the same place.

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;
}
薄荷梦 2024-10-05 05:12:19

基于亚伦的选择器建议,我真的很喜欢现在做一个简单的临时调度方法的想法。它成功地避免了处理错误选项的可能性,并提供了清晰的关注点分解。当然,我可以想象一个用例,您希望首先为每个选项执行其他操作,例如实例化一个对象并向其传递选项字符串,就像 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")
    }
}

Building on Aaron's selector suggestion, I really like the idea now of doing a simple ad-hoc dispatch method. It successfully avoids the possibility of handling the wrong option as well as providing a clean factorization of concerns. Of course, I could imagine a use case where you want to do something else first for each option, such as instantiating an object and passing it the option string much like Toro's answer.

Here's a simple dispatch that calls methods such as '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")
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文