在操作表中添加UIPickerView和按钮-如何?


120

我的应用程序需要在操作表中添加以下内容。

  • UIToolbar
  • UIToolbar上的按钮
  • UIPicker控制

我包括一张图片,以了解我的要求。

替代文字

您能否解释一下,如何实现?


3
感谢您发布这个有趣的问题!
Tuyen Nguyen

我建议您使用EAActionSheetPicker而不是去管理actionSheet,pickerView等。它确实清理了很多代码。
ebandersen

@eckyzero不幸的是,EAActionSheetPicker在iOS 7中似乎已损坏,有很多错误都以“无效的上下文0x0”开头。
Magnus

当然,这确实是一件好事……骗我,我的ios太难了
ChuckKelly 2013年

EAActionSheetPicker不再起作用。
osxdirk

Answers:


25

iOS 7更新

适用于UIActionSheet的Apple文档UIActionSheet is not designed to be subclassed, nor should you add views to its hierarchy

我建议不要尝试自定义ActionSheet的内容,因为它可能导致iOS 7中出现严重的无效上下文错误。我花了几个小时解决这个问题,最终决定采用其他方法。我用一个包含简单表格视图的模式视图控制器替换了显示动作表的调用。

有很多方法可以完成此任务。这是我在当前项目中刚刚实现的一种方法。很好,因为我可以在5个或6个不同的屏幕之间重用它,所有用户都可以从这些屏幕中进行选择。

  1. 创建一个新的UITableViewController子类, SimpleTableViewController
  2. 在情节提要中创建UITableViewController(嵌入在导航控制器中),并将其自定义类设置为SimpleTableViewController。
  3. 为SimpleTableViewController的导航控制器提供一个Storyboard ID“ SimpleTableVC”。
  4. 在SimpleTableViewController.h中,创建一个NSArray属性,该属性将表示表中的数据。
  5. 同样在SimpleTableViewController.h中,SimpleTableViewControllerDelegate使用必需的方法itemSelectedatRow:和一个弱属性(称为委托类型)创建协议id<SimpleTableViewControllerDelegate>。这就是我们将选择传递回父控制器的方式。
  6. 在SimpleTableViewController.m,实现tableview中的数据源和委托方法,要求itemSelectedatRow:tableView:didSelectRowAtIndexPath:

这种方法的另一个好处是可以相当重用。若要使用,请在您的ViewController.h中导入SimpleTableViewController类,遵循SimpleTableViewDelegate并实现该itemSelectedAtRow:方法。然后,要打开模式,只需实例化一个新的SimpleTableViewController,设置表数据并委托,然后将其呈现。

UINavigationController *navigationController = (UINavigationController *)[self.storyboard instantiateViewControllerWithIdentifier:@"SimpleTableVC"];
SimpleTableViewController *tableViewController = (SimpleTableViewController *)[[navigationController viewControllers] objectAtIndex:0];
tableViewController.tableData = self.statesArray;
tableViewController.navigationItem.title = @"States";
tableViewController.delegate = self;
[self presentViewController:navigationController animated:YES completion:nil];

我创建一个简单的示例并将其发布在github上

另请参阅显示操作表会导致CGContext无效上下文错误


2
啊iOS 7!您破坏了到目前为止开发的所有内容。:(
Sagar R. Kothari 2013年

@Kyle您能通过告诉您使用什么方法来扩展答案吗?谢谢
Daniel Sanchez 2013年

1
@DanielSanchez更新了替代建议和代码示例。
凯尔·克莱格

3
恕我直言,一种更为优雅的解决方案是将文本字段的输入视图设置为UIPickerView,将其附件设置为UIToolbar。您可以查看QuickDialog项目中的示例。
史蒂夫·摩泽尔

111

另一种解决方案:

  • 没有工具栏,但有分段控件(eyecandy)

    UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:nil 
                                                        delegate:nil
                                                        cancelButtonTitle:nil
                                                        destructiveButtonTitle:nil
                                                        otherButtonTitles:nil];
    
    [actionSheet setActionSheetStyle:UIActionSheetStyleBlackTranslucent];
    
    CGRect pickerFrame = CGRectMake(0, 40, 0, 0);
    
    UIPickerView *pickerView = [[UIPickerView alloc] initWithFrame:pickerFrame];
    pickerView.showsSelectionIndicator = YES;
    pickerView.dataSource = self;
    pickerView.delegate = self;
    
    [actionSheet addSubview:pickerView];
    [pickerView release];
    
    UISegmentedControl *closeButton = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObject:@"Close"]];
    closeButton.momentary = YES; 
    closeButton.frame = CGRectMake(260, 7.0f, 50.0f, 30.0f);
    closeButton.segmentedControlStyle = UISegmentedControlStyleBar;
    closeButton.tintColor = [UIColor blackColor];
    [closeButton addTarget:self action:@selector(dismissActionSheet:) forControlEvents:UIControlEventValueChanged];
    [actionSheet addSubview:closeButton];
    [closeButton release];
    
    [actionSheet showInView:[[UIApplication sharedApplication] keyWindow]];
    
    [actionSheet setBounds:CGRectMake(0, 0, 320, 485)];

1
我收到UIApplication可能不响应主窗口警告,并且应用程序终止。
Mahesh Babu 2010年

我正在尝试使用UIPickerView,但无法将其集成到我的应用程序中。我希望在单击其动作已在MYViewController:UIViewController中注册的按钮时显示UIPickerView。在动作方法中,我已将上面的代码放置在pickerview中。我该怎么办?请帮忙。
Namratha 2011年

1
实际上是fredrik,您应该使用[[UIApplication sharedApplication] keyWindow]。编辑源以更改该设置。
埃里克·戈德堡

3
Van Du Tran-(void)dismissActionSheet:(UISegmentedControl *)sender {UIActionSheet actionSheet =(UIActionSheet)[sender superview ]; [actionSheet dismissWithClickedButtonIndex:0动画:是];}
WINSergey 2013年

1
请注意:这会导致iOS的7看到许多CGContext上的错误stackoverflow.com/questions/19129091/...
凯尔·克莱格

75

即使这个问题很老,我也会很快提到我已经将ActionSheetPicker类与便捷函数组合在一起,因此您可以在一行中生成带有UIPickerView的ActionSheet。它基于此问题答案的代码。

编辑:现在它还支持使用DatePicker和DistancePicker。


UPD:

不建议使用此版本:请改用ActionSheetPicker-3.0

动画


1
超级...在我的应用程序中使用它。
迈克尔·莫里森,

太棒了 现在使用这个。
詹姆斯·斯基德莫尔

@Sick如果我使用您的代码,我需要在我的项目中添加您的名字吗,谢谢
vinothp

您好,我在我的应用程序中使用了此类,效果很好。谢谢。我有个问题。在此ActionSheetDatePicker模式下,您可以在顶部的工具栏中添加多个按钮。正常ActionSheetStringPicker也可以吗?
Isuru

可以选择吗?
凯尔·克莱格

32

是的!我终于找到了。

在您的按钮单击事件上实现以下代码,以弹出问题图片中给出的操作表。

UIActionSheet *aac = [[UIActionSheet alloc] initWithTitle:@"How many?"
                                             delegate:self
                                    cancelButtonTitle:nil
                               destructiveButtonTitle:nil
                                    otherButtonTitles:nil];

UIDatePicker *theDatePicker = [[UIDatePicker alloc] initWithFrame:CGRectMake(0.0, 44.0, 0.0, 0.0)];
if(IsDateSelected==YES)
{
    theDatePicker.datePickerMode = UIDatePickerModeDate;
    theDatePicker.maximumDate=[NSDate date];
}else {
    theDatePicker.datePickerMode = UIDatePickerModeTime;
}

self.dtpicker = theDatePicker;
[theDatePicker release];
[dtpicker addTarget:self action:@selector(dateChanged) forControlEvents:UIControlEventValueChanged];

pickerDateToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
pickerDateToolbar.barStyle = UIBarStyleBlackOpaque;
[pickerDateToolbar sizeToFit];

NSMutableArray *barItems = [[NSMutableArray alloc] init];

UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
[barItems addObject:flexSpace];

UIBarButtonItem *doneBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(DatePickerDoneClick)];
[barItems addObject:doneBtn];

[pickerDateToolbar setItems:barItems animated:YES];

[aac addSubview:pickerDateToolbar];
[aac addSubview:dtpicker];
[aac showInView:self.view];
[aac setBounds:CGRectMake(0,0,320, 464)];

嗨,萨加尔,感谢代码,DatePickerDoneClick按钮事件,如何声明它
Apache

sagar,如何取消弹出的操作表,以及将选定的值添加到文本字段中,谢谢
Apache

[aac dimisswithclickedindex] //类似于此方法。(见我已经把一个完成按钮在动作片和添加完成按钮目标-选择在与选择的地方dimissal代码。
萨加尔R.科塔

感谢您的回复,萨加尔,我添加了[aac dismissWithClickedButtonIndex];但我收到警告:“ UIActionSheet”可能不会响应“ -dismissWithClickedButtonIndex”,然后为选择器“ DatePickerDoneClick”创建新方法,如下所示-(void)DatePickerDoneClick {NSLog(@“ Done clicked”); 我应该怎么做,所以当我点击完成按钮UIActionSheet(aac)时,textfield(ratings.text)会被选择器中的选定值填充。感谢sagar
Apache

1
@Spark可以给出一些有关获取选择器文本的想法..感谢您的发帖+1
vinothp

10

Marcio对这个问题的出色解决方案对我向UIActionSheet添加任何类型的子视图都大有帮助。

由于尚不完全清楚的原因,UIActionSheet的边界只能在显示后设置。sagar和marcio的解决方案都通过将setBounds:CGRectMake(...)消息显示在操作表成功解决了此问题。

但是,在显示工作表之后设置UIActionSheet边界会在ActionSheet出现时弹出一个过渡,过渡到视图中,然后仅滚动显示最后40个像素左右。

添加子视图后调整UIPickerView的大小时,建议将发送到actionSheet的setBounds消息包装在动画块中。这将使actionSheet的入口显得更平滑。

UIActionSheet *actionSheet = [[[UIActionSheet alloc] initWithTitle:nil delegate:nil cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil];


// add one or more subviews to the UIActionSheet
// this could be a UIPickerView, or UISegmentedControl buttons, or any other 
// UIView.  Here, let's just assume it's already set up and is called 
// (UIView *)mySubView
[actionSheet addSubview:myView];

// show the actionSheet
[actionSheet showInView:[UIApplication mainWindow]];


// Size the actionSheet with smooth animation
    [UIView beginAnimations:nil context:nil];
    [actionSheet setBounds:CGRectMake(0, 0, 320, 485)];
    [UIView commitAnimations]; 

6
这是一个很好的提示。在ios 4及更高版本中,尽管按照文档所述,这种动画风格还是“模糊的”。在iOS 4或更高版本上,请尝试以下操作:UIView animateWithDuration:0.3f delay:0 options:UIViewAnimationOptionCurveEaseInOut动画:^ {[actionSheet setBounds:CGRectMake(0,0,320,485)];}完成:NULL];
ceperry11年

9

对于那些想找到DatePickerDoneClick函数的家伙……这是消除操作表的简单代码。显然,aac应该是一个ivar(在您的实现.h文件中使用的一个)


- (void)DatePickerDoneClick:(id)sender{
    [aac dismissWithClickedButtonIndex:0 animated:YES];
}

9

我真的不明白为什么UIPickerView会在UIActionSheet。这似乎是一个杂乱无章的解决方案,可以在将来的iOS版本中打破。(我以前在应用程序中遇到过这样的问题,在UIPickerView第一次点击时就没有出现,必须重新输入-带有怪异的怪癖UIActionSheet)。

我所做的只是简单地实现一个UIPickerView,然后将其作为子视图添加到我的视图中,并对其进行动画处理,就像它像一个动作表一样呈现。

/// Add the PickerView as a private variable
@interface EMYourClassName ()

@property (nonatomic, strong) UIPickerView *picker;
@property (nonatomic, strong) UIButton *backgroundTapButton;

@end

///
/// This is your action which will present the picker view
///
- (IBAction)showPickerView:(id)sender {

    // Uses the default UIPickerView frame.
    self.picker = [[UIPickerView alloc] initWithFrame:CGRectZero];

    // Place the Pickerview off the bottom of the screen, in the middle set the datasource delegate and indicator
    _picker.center = CGPointMake([[UIScreen mainScreen] bounds].size.width / 2.0, [[UIScreen mainScreen] bounds].size.height + _picker.frame.size.height);
    _picker.dataSource = self;
    _picker.delegate = self;
    _picker.showsSelectionIndicator = YES;

    // Create the toolbar and place it at -44, so it rests "above" the pickerview.
    // Borrowed from @Spark, thanks!
    UIToolbar *pickerDateToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, -44, 320, 44)];
    pickerDateToolbar.barStyle = UIBarStyleBlackTranslucent;
    [pickerDateToolbar sizeToFit];

    NSMutableArray *barItems = [[NSMutableArray alloc] init];

    UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
    [barItems addObject:flexSpace];

    // The action can whatever you want, but it should dimiss the picker.
    UIBarButtonItem *doneBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(backgroundTapped:)];
    [barItems addObject:doneBtn];

    [pickerDateToolbar setItems:barItems animated:YES];
    [_picker addSubview:pickerDateToolbar];

    // If you have a UITabBarController, you should add the picker as a subview of it
    // so it appears to go over the tabbar, not under it. Otherwise you can add it to 
    // self.view
    [self.tabBarController.view addSubview:_picker];

    // Animate it moving up
    [UIView animateWithDuration:.3 animations:^{
        [_picker setCenter:CGPointMake(160, [[UIScreen mainScreen] bounds].size.height - 148)]; //148 seems to put it in place just right.
    } completion:^(BOOL finished) {
        // When done, place an invisible button on the view behind the picker, so if the
        // user "taps to dismiss" the picker, it will go away. Good user experience!
        self.backgroundTapButton = [UIButton buttonWithType:UIButtonTypeCustom];
        _backgroundTapButton.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
        [_backgroundTapButton addTarget:self action:@selector(backgroundTapped:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:_backgroundTapButton];
    }];

}

// And lastly, the method to hide the picker.  You should handle the picker changing
// in a method with UIControlEventValueChanged on the pickerview.
- (void)backgroundTapped:(id)sender {

    [UIView animateWithDuration:.3 animations:^{
        _picker.center = CGPointMake(160, [[UIScreen mainScreen] bounds].size.height + _picker.frame.size.height);
    } completion:^(BOOL finished) {
        [_picker removeFromSuperview];
        self.picker = nil;
        [self.backgroundTapButton removeFromSuperview];
        self.backgroundTapButton = nil;
    }];
}

非常感谢。您是否发现此功能适用于iOS 7?
2013年

1
+1可显示动画的出色简单选择器视图。但是您在代码中有错误。视图backgroundTapButton生成在选择器视图的顶部,用于阻止用户交互的选择器视图。您真正想做的是使该视图显示在选择器视图尚未填充的剩余屏幕空间上((您无法像您的意思那样在选择器视图后面创建视图)
aZtraL-EnForceR

7

要添加到marcio的出色解决方案中,dismissActionSheet:可以如下实现。

  1. 将ActionSheet对象添加到.h文件中,对其进行合成并在.m文件中进行引用。
  2. 将此方法添加到您的代码中。

    - (void)dismissActionSheet:(id)sender{
      [_actionSheet dismissWithClickedButtonIndex:0 animated:YES];
      [_myButton setTitle:@"new title"]; //set to selected text if wanted
    }

3

我认为这是最好的方法。

ActionSheetPicker-3.0

几乎每个人都建议,但是使用了块,这是一个不错的选择!


好吧,如果您阅读上面的内容,您将会发现该ActionSheetPicker类首先是由于该线程而出现的。是的,这是最好的方法,这要归功于公认的解决方案(¬_¬)。 stackoverflow.com/questions/1262574/...
OpenUserX03

2
看起来苹果即将开始执行其规则,即在收到此错误消息时不应将操作表子类化-“ <错误>:CGContextSetFillColorWithColor:无效的上下文0x0。这是一个严重的错误。此应用程序或它使用的库,则使用无效的上下文,从而导致系统稳定性和可靠性整体下降。此通知出于礼貌:请解决此问题。在即将进行的更新中,它将成为致命错误。”
Stuart P.

@StuartP。看到我的答案
凯尔·克莱格

2

从iOS 8开始,您将无法使用它,因为苹果更改了的内部实现UIActionSheet。请参考Apple文档

子类注释

UIActionSheet并非旨在被子类化,也不 应将视图添加到其层次结构中。如果您需要提供比UIActionSheet API提供的自定义表更多的自定义表,则可以创建自己的表并使用presentViewController:animated:completion:模态地呈现它。


1

我喜欢Wayfarer采取的方法,并且屈服了,但是(像aZtral一样)发现它不起作用,因为backgroundTapButton是唯一响应用户交互的元素。这导致我将他的所有三个子视图:_picker,_pickerToolbar和backgroundTapButton放在一个包含视图(弹出窗口)中,然后在屏幕上和屏幕外对其进行动画处理。我还需要_pickerToolbar上的“取消”按钮。这是弹出视图的相关代码元素(您需要提供自己的选择器数据源和委托方法)。

#define DURATION            0.4
#define PICKERHEIGHT        162.0
#define TOOLBARHEIGHT       44.0

@interface ViewController ()
@property (nonatomic, strong) UIView        *popup;
@property (nonatomic, strong) UIPickerView  *picker;
@property (nonatomic, strong) UIToolbar     *pickerToolbar;
@property (nonatomic, strong) UIButton      *backgroundTapButton;
@end

-(void)viewDidLoad {
    // These are ivars for convenience
    rect = self.view.bounds;
    topNavHeight = self.navigationController.navigationBar.frame.size.height;
    bottomNavHeight = self.navigationController.toolbar.frame.size.height;
    navHeights = topNavHeight + bottomNavHeight;
}

-(void)showPickerView:(id)sender {
    [self createPicker];
    [self createToolbar];

    // create view container
    _popup = [[UIView alloc] initWithFrame:CGRectMake(0.0, topNavHeight, rect.size.width, rect.size.height - navHeights)];
    // Initially put the centre off the bottom of the screen
    _popup.center = CGPointMake(rect.size.width / 2.0, rect.size.height + _popup.frame.size.height / 2.0);
    [_popup addSubview:_picker];
    [_popup insertSubview:_pickerToolbar aboveSubview:_picker];

    // Animate it moving up
    // This seems to work though I am not sure why I need to take off the topNavHeight
    CGFloat vertCentre = (_popup.frame.size.height - topNavHeight) / 2.0;

    [UIView animateWithDuration:DURATION animations:^{
        // move it to a new point in the middle of the screen
        [_popup setCenter:CGPointMake(rect.size.width / 2.0, vertCentre)];
    } completion:^(BOOL finished) {
        // When done, place an invisible 'button' on the view behind the picker,
        // so if the user "taps to dismiss" the picker, it will go away
        self.backgroundTapButton = [UIButton buttonWithType:UIButtonTypeCustom];
        _backgroundTapButton.frame = CGRectMake(0, 0, _popup.frame.size.width, _popup.frame.size.height);
        [_backgroundTapButton addTarget:self action:@selector(doneAction:) forControlEvents:UIControlEventTouchUpInside];
        [_popup insertSubview:_backgroundTapButton belowSubview:_picker];
        [self.view addSubview:_popup];
    }];
}

-(void)createPicker {
    // To use the default UIPickerView frame of 216px set frame to CGRectZero, but we want the 162px height one
    CGFloat     pickerStartY = rect.size.height - navHeights - PICKERHEIGHT;
    self.picker = [[UIPickerView alloc] initWithFrame:CGRectMake(0.0, pickerStartY, rect.size.width, PICKERHEIGHT)];
    _picker.dataSource = self;
    _picker.delegate = self;
    _picker.showsSelectionIndicator = YES;
    // Otherwise you can see the view underneath the picker
    _picker.backgroundColor = [UIColor whiteColor];
    _picker.alpha = 1.0f;
}

-(void)createToolbar {
    CGFloat     toolbarStartY = rect.size.height - navHeights - PICKERHEIGHT - TOOLBARHEIGHT;
    _pickerToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, toolbarStartY, rect.size.width, TOOLBARHEIGHT)];
    [_pickerToolbar sizeToFit];

    NSMutableArray *barItems = [[NSMutableArray alloc] init];
    UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelAction:)];
    [barItems addObject:cancelButton];

    // Flexible space to make the done button go on the right
    UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
    [barItems addObject:flexSpace];

    // The done button
    UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(doneAction:)];
    [barItems addObject:doneButton];
    [_pickerToolbar setItems:barItems animated:YES];
}

// The method to process the picker, if we have hit done button
- (void)doneAction:(id)sender {
    [UIView animateWithDuration:DURATION animations:^{
        _popup.center = CGPointMake(rect.size.width / 2.0, rect.size.height + _popup.frame.size.height / 2.0);
    } completion:^(BOOL finished) { [self destroyPopup]; }];
    // Do something to process the returned value from your picker
}

// The method to process the picker, if we have hit cancel button
- (void)cancelAction:(id)sender {
    [UIView animateWithDuration:DURATION animations:^{
        _popup.center = CGPointMake(rect.size.width / 2.0, rect.size.height + _popup.frame.size.height / 2.0);
    } completion:^(BOOL finished) { [self destroyPopup]; }];
}

-(void)destroyPopup {
    [_picker removeFromSuperview];
    self.picker = nil;
    [_pickerToolbar removeFromSuperview];
    self.pickerToolbar = nil;
    [self.backgroundTapButton removeFromSuperview];
    self.backgroundTapButton = nil;
    [_popup removeFromSuperview];
    self.popup = nil;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.