Mi aplicación requiere que se agreguen las siguientes cosas en una hoja de acción.
- UIToolbar
- Botón en UIToolbar
- Control de UIPicker
He incluido una imagen para comprender mis requisitos.
¿Podría explicar cómo se puede implementar?
Mi aplicación requiere que se agreguen las siguientes cosas en una hoja de acción.
He incluido una imagen para comprender mis requisitos.
¿Podría explicar cómo se puede implementar?
Respuestas:
Actualización para iOS 7
Documentos de Apple para UIActionSheet :UIActionSheet is not designed to be subclassed, nor should you add views to its hierarchy
Recomiendo no intentar personalizar el contenido de una hoja de acción, ya que puede provocar errores graves de contexto no válido en iOS 7. Acabo de pasar unas horas trabajando en este problema y finalmente decidí adoptar un enfoque diferente. Reemplacé la llamada para mostrar la hoja de acción con un controlador de vista modal que contiene una vista de tabla simple.
Hay muchas formas de lograr esto. Aquí hay una forma que acabo de implementar en un proyecto actual. Es bueno porque puedo reutilizarlo entre 5 o 6 pantallas diferentes donde todos los usuarios pueden seleccionar de una lista de opciones.
SimpleTableViewController
.SimpleTableViewControllerDelegate
con un método requerido itemSelectedatRow:
y una propiedad débil llamada delegado de tipo id<SimpleTableViewControllerDelegate>
. Así es como devolveremos la selección al controlador padre.itemSelectedatRow:
a tableView:didSelectRowAtIndexPath:
.Este enfoque tiene la ventaja adicional de ser bastante reutilizable. Para usar, importe la clase SimpleTableViewController en su ViewController.h, cumpla con SimpleTableViewDelegate e implemente el itemSelectedAtRow:
método. Luego, para abrir el modal, simplemente cree una nueva instancia de SimpleTableViewController, configure los datos de la tabla y delegue, y preséntelo.
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];
Creo un ejemplo simple y lo publiqué en github .
Consulte también Mostrar la hoja de acciones provoca errores de contexto no válido CGContext .
Una solución más:
sin barra de herramientas pero con un control segmentado (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)];
Aunque esta pregunta es antigua, mencionaré rápidamente que he reunido una clase ActionSheetPicker con una función de conveniencia, para que pueda generar una ActionSheet con un UIPickerView en una línea. Se basa en el código de las respuestas a esta pregunta.
Editar: ahora también admite el uso de DatePicker y DistancePicker.
ActionSheetDatePicker
modo, puede agregar varios botones a la barra de herramientas en la parte superior. ¿Es esto posible también con lo normal ActionSheetStringPicker
?
¡Sí! Finalmente lo encuentro.
implemente el siguiente código en su evento de clic de botón, para que aparezca la hoja de acción como se muestra en la imagen de la pregunta.
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)];
La excelente solución de Marcio a esta pregunta fue de gran ayuda para mí al agregar subvistas de cualquier tipo a una UIActionSheet.
Por razones que (todavía) no están del todo claras para mí, los límites de UIActionSheet solo se pueden establecer después de que se haya mostrado; las soluciones de sagar y marcio abordan con éxito esto con un mensaje setBounds: CGRectMake (...) que se envía a la hoja de acciones después de que se muestra.
Sin embargo, establecer los límites de UIActionSheet después de que se haya mostrado la hoja crea una transición brusca cuando aparece ActionSheet, donde "aparece" a la vista, y luego solo se desplaza sobre los últimos 40 píxeles aproximadamente.
Al dimensionar un UIPickerView después de agregar subvistas, recomiendo envolver el mensaje setBounds enviado a actionSheet dentro de un bloque de animación. Esto hará que la entrada de actionSheet parezca más suave.
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];
Para aquellos tipos que buscan encontrar la función DatePickerDoneClick ... aquí está el código simple para descartar la Hoja de Acción. Obviamente, aac debería ser un ivar (el que va en su archivo .h de implementación)
- (void)DatePickerDoneClick:(id)sender{
[aac dismissWithClickedButtonIndex:0 animated:YES];
}
Realmente no entiendo por qué UIPickerView
está entrando un UIActionSheet
. Esta parece ser una solución desordenada y hacky, que puede romperse en una futura versión de iOS. (He tenido cosas como esta interrupción en una aplicación antes, donde el UIPickerView
no se presentaba en el primer toque y tenía que volver a insertarse, peculiaridades extrañas con UIActionSheet
).
Lo que hice fue simplemente implementar un UIPickerView
y luego agregarlo como una subvista a mi vista, y animarlo moviéndose hacia arriba como si se presentara como una hoja de acción.
/// 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;
}];
}
Para agregar a la increíble solución de marcio, dismissActionSheet:
se puede implementar de la siguiente manera.
Agrega este método a tu código.
- (void)dismissActionSheet:(id)sender{
[_actionSheet dismissWithClickedButtonIndex:0 animated:YES];
[_myButton setTitle:@"new title"]; //set to selected text if wanted
}
Creo que esta es la mejor forma de hacerlo.
Es más o menos lo que todos sugieren, pero usa bloques, ¡lo cual es un buen toque!
Desde iOS 8, no puede, no funciona porque Apple cambió la implementación interna de UIActionSheet
. Consulte la documentación de Apple :
Notas de subclasificación
UIActionSheet no está diseñado para ser subclasificado, ni debe agregar vistas a su jerarquía . Si necesita presentar una hoja con más personalización que la proporcionada por la API UIActionSheet, puede crear la suya propia y presentarla de manera modal con presentViewController: animated: complete :.
Me gustó el enfoque adoptado por Wayfarer y flexaddicted, pero descubrí (como aZtral) que no funcionaba ya que backgroundTapButton era el único elemento que respondía a la interacción del usuario. Esto me llevó a poner sus tres subvistas: _picker, _pickerToolbar y backgroundTapButton dentro de una vista contenedora (emergente) que luego se animó dentro y fuera de la pantalla. También necesitaba un botón Cancelar en _pickerToolbar. Aquí están los elementos de código relevantes para la vista emergente (necesita proporcionar su propia fuente de datos de selector y métodos delegados).
#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;
}