【问题标题】:Add UIPickerView & a Button in Action sheet - How?在操作表中添加 UIPickerView 和按钮 - 如何?
【发布时间】:2010-11-18 18:05:35
【问题描述】:

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

  • UI工具栏
  • UIToolbar 上的按钮
  • UIPicker 控件

我添加了一张图片来了解我的要求。

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

【问题讨论】:

  • 感谢您发布这个有趣的问题!
  • 我建议使用EAActionSheetPicker,而不是管理actionSheet、pickerView 等。它确实清理了你的代码很多。
  • @eckyzero 不幸的是,EAActionSheetPicker 在 iOS 7 中似乎被破坏了,有很多错误都以“invalid context 0x0”开头。
  • 当然它是真实的好...骗我,你的 ios b 太难了
  • EAActionSheetPicker 不再工作。

标签: ios iphone uipickerview actionsheetpicker


【解决方案1】:

另一种解决方案:

  • 没有工具栏,但有一个分段控件(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)];
    

【讨论】:

  • 我得到 UIApplication 可能无法响应主窗口警告并且应用程序终止。
  • 我正在尝试使用 UIPickerView 但无法将其集成到我的应用程序中。我希望 UIPickerView 在单击按钮时显示,该按钮的操作已在 MYViewController:UIViewController 中注册。在action方法中我放了上面pickerview的代码。我还应该做什么?请帮忙。
  • 其实fredrik,你应该使用[[UIApplication sharedApplication] keyWindow]。编辑源以改变这一点。
  • Van Du Tran - (void)dismissActionSheet:(UISegmentedControl*)sender{ UIActionSheet actionSheet = (UIActionSheet)[sender superview]; [actionSheetdismissWithClickedButtonIndex:0 动画:YES]; }
  • 注意:这会导致 iOS 7 中出现许多 CGContext 错误。请参阅 stackoverflow.com/questions/19129091/…
【解决方案2】:

尽管这个问题很老,但我很快就会提到我已经将ActionSheetPicker class 与便利功能放在一起,因此您可以在一行中生成带有 UIPickerView 的 ActionSheet。它基于此问题的答案中的代码。

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


更新:

此版本已弃用:改用ActionSheetPicker-3.0

【讨论】:

  • 超级...在我的应用程序中使用它。
  • @Sick 如果我使用你的代码,我需要在我的项目中添加你的名字吗,谢谢
  • 您好,我在我的应用程序中使用了这个类,效果很好。谢谢。我有个问题。在ActionSheetDatePicker 模式下,您可以在顶部的工具栏中添加多个按钮。普通的ActionSheetStringPicker 也可以做到这一点吗?
  • 可以多选吗?
  • 它在 ios 7.1 中的突破 :(
【解决方案3】:

是的!终于找到了。

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

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

【讨论】:

  • 嗨 sagar,感谢代码,DatePickerDoneClick 按钮事件,如何声明它谢谢
  • sagar,如何关闭弹出的操作表,并将所选值添加到文本字段中,谢谢
  • [aac dimisswithclickedindex] // 类似这个方法。 (请参阅我在操作表中放置了一个完成按钮并添加完成按钮目标 - 选择器和在选择器中放置解雇代码。
  • 感谢 sagar 的回复,我添加了 [aac dismissWithClickedButtonIndex];但我收到警告:'UIActionSheet' 可能不会响应'-dismissWithClickedButtonIndex' 然后我为选择器'DatePickerDoneClick' 创建新方法,如下所示 - (void) DatePickerDoneClick { NSLog(@"Done clicked"); Ratings.text = pickerView objectAtIndex:row] } 我应该做什么,所以当我点击完成按钮 UIActionSheet(aac) 关闭和 textfield(ratings.text) 填充从选择器中选择的值谢谢 sagar
  • @Spark 是否可以提供一些关于获取选择器文本的想法。感谢您的帖子+1
【解决方案4】:

iOS 7 更新

Apple docs for UIActionSheet: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 中,使用必需的方法 itemSelectedatRow: 和称为 id<SimpleTableViewControllerDelegate> 类型的委托的弱属性创建协议 SimpleTableViewControllerDelegate。这就是我们将选择传递回父控制器的方式。
  6. 在SimpleTableViewController.m中,实现tableview数据源和委托方法,在tableView:didSelectRowAtIndexPath:中调用itemSelectedatRow:

这种方法还有一个额外的好处是可重复使用。要使用,请在 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];

我创建了一个简单的例子和​​posted it on github

另见Showing actionsheet causes CGContext invalid context errors

【讨论】:

  • 啊,iOS 7!你毁掉了发展到现在的一切。 :(
  • @Kyle 你能通过告诉你的方法来扩展你的答案吗?谢谢
  • @DanielSanchez 更新了替代建议和代码示例。
  • 恕我直言,一个更优雅的解决方案是将文本字段的输入视图设置为 UIPickerView 并将其附件设置为 UIToolbar。您可以查看 QuickDialog 项目以获取示例。
【解决方案5】:

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

【讨论】:

  • 这是一个很棒的提示。在 ios 4 及更高版本下,尽管根据文档,这种动画风格是“不鼓励的”。在 iOS 4 或更高版本下,试试这个: UIView animateWithDuration:0.3f delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{[actionSheet setBounds:CGRectMake(0,0,320,485)];} completion:NULL];
【解决方案6】:

对于那些想找到 DatePickerDoneClick 函数的人...这里是关闭操作表的简单代码。显然 aac 应该是一个 ivar(在你的实施 .h 文件中)


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

【讨论】:

    【解决方案7】:

    我真的不明白为什么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?
    • +1 用于动画到屏幕的简单的选择器视图。但是您的代码中有错误。视图 backgroundTapButton 生成在选取器视图之上,用于阻止选取器视图与用户交互。您真正要做的是在选择器视图尚未填充的剩余屏幕空间上制作此视图...(您不能像您的意思那样在选择器视图后面制作视图)
    【解决方案8】:

    要添加到 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
      }
      

    【讨论】:

      【解决方案9】:

      我认为这是最好的方法。

      ActionSheetPicker-3.0

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

      【讨论】:

      • 好吧,如果您阅读上面的内容,您会看到这个 ActionSheetPicker 类首先出现,因为这个线程。所以是的,这是最好的方法,这要归功于公认的解决方案(¬_¬)。 stackoverflow.com/questions/1262574/…
      • 看起来苹果即将开始执行他们的规则,即操作表不应被子类化,因为我收到此错误消息 - “ : CGContextSetFillColorWithColor: invalid context 0x0。这是一个严重的错误。此应用程序或其使用的库正在使用无效的上下文,从而导致系统稳定性和可靠性的整体下降。此通知是出于礼貌:请修复此问题。它将在即将到来的更新中成为致命错误。 "
      • @StuartP。看我的回答
      【解决方案10】:

      从 iOS 8 开始,您不能这样做,因为 Apple 更改了 UIActionSheet 的内部实现,所以它不起作用。请参考Apple Documentation

      子类化注释

      UIActionSheet 并非设计为子类化,也不 您是否应该将视图添加到其层次结构中。如果您需要呈现比 UIActionSheet API 提供的自定义更多的表单,您可以创建自己的表单并使用 presentViewController:animated:completion: 以模态方式呈现它。

      【讨论】:

        【解决方案11】:

        我喜欢 Wayfarer 和 flexaddicted 采用的方法,但发现(如 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;
        }
        

        【讨论】:

          猜你喜欢
          • 2021-04-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多