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

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

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

我已经包括了一个图像,以了解我的要求。

alt text

你能解释一下,这是怎么实现的吗?

116121 次浏览

是的,我终于找到了。

实现下面的代码对您的按钮点击事件,弹出行动表中给出的问题的形象。

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

还有一个办法:

  • no toolbar but a segmented control (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)];
    

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

由于我还不完全清楚的原因,UIActionsheet 的界限只能在它显示之后才能设置; Sagar 和 marcio 的解决方案都成功地用 setBound: CGRectMake (...)消息来解决这个问题,该消息被发送到所显示的操作表 之后

然而,在工作表显示之后设置 UIActionsheet 边界会在 ActionSheet 出现时产生一个跳跃的转换,在那里它“弹出”到视图中,然后只滚动到最后40像素左右。

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

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

对于那些试图找到 DatePickerDoneClick 函数的人来说... ... 这里有一个简单的代码来解除操作表。显然,aac 应该是一个 ivar (在您的实现中使用的那个)。H 档案)


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

尽管这个问题已经很老了,但我还是要快速提一下,我已经将带有方便函数的 ActionSheetPicker 类组合在一起,这样您就可以在一行中生成带有 UIPickerView 的 ActionSheet。它是基于这个问题的答案的代码。

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


UPD:

此版本不推荐使用: 改为使用 ActionSheetPicker-3.0

animation

To add to marcio's awesome solution, dismissActionSheet: can be implemented as follows.

  1. Add an ActionSheet object to your .h file, synthesize it and reference it in your .m file.
  2. 将此方法添加到代码中。

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

我觉得这是最好的办法。

ActionSheetPicker-3.0

这几乎是每个人建议,但使用块,这是一个很好的触摸!

我真的不明白为什么 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;
}];
}

IOS7更新

UIActionsheet 的苹果文档 : UIActionSheet is not designed to be subclassed, nor should you add views to its hierarchy

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

有很多方法可以实现这一点。下面是我在当前项目中实现的一种方法。这很好,因为我可以在5或6个不同的屏幕之间重用它,所有用户都可以从选项列表中进行选择。

  1. 创建一个新的 UITableViewController 子类 SimpleTableViewController
  2. 在故事板中创建一个 UITableViewController (嵌入在导航控制器中) ,并将其自定义类设置为 SimpleTableViewController。
  3. Give the navigation controller for SimpleTableViewController a Storyboard ID of "SimpleTableVC".
  4. 在 SimpleTableViewController.h 中,创建一个表示表中数据的 NSArray 属性。
  5. 同样在 SimpleTableViewController.h 中,使用所需的方法 itemSelectedatRow:创建一个协议 SimpleTableViewControllerDelegate,并创建一个称为 id<SimpleTableViewControllerDelegate>类型委托的弱属性。这是我们将选择传递回父控制器的方式。
  6. 在 SimpleTableViewController.m 中,实现 tableview 数据源和委托方法,在 tableView:didSelectRowAtIndexPath:中调用 itemSelectedatRow:

这种方法还有一个额外的好处,那就是可以很好地重用。要使用,请在 ViewController.h 中导入 SimpleTableViewController 类,遵循 SimpleTableViewCommittee,并实现 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 无效上下文错误

我喜欢 Wayfarer 的方法,但是发现(像 aZtral 一样)它不能作为背景,TapButton 是唯一响应用户交互的元素。这导致我把他的三个子视图: _ picker,_ pickerToolbar 和 backoundTapButton 放在一个包含视图(弹出窗口)中,然后在屏幕上和屏幕外进行动画处理。我还需要 _ pickerToolbar 上的 Cancel 按钮。下面是弹出视图的相关代码元素(您需要提供自己的选择器数据源和委托方法)。

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

由于 iOS8,你不能,它不工作,因为苹果改变了内部的 UIActionSheet实现。请参考 苹果文档:

子类注释

UIActionsheet 不是设计成子类化的,< strong > 也不是 应该将视图添加到它的层次结构 。如果需要比 UIActionSheetAPI 提供更多自定义的工作表,可以创建自己的工作表,并通过 presViewController: Animated: complete: 模式化地显示它。