Moja aplikacja wymaga dodania następujących elementów w arkuszu działań.
- UIToolbar
- Przycisk na UIToolbar
- Kontrola UIPicker
Dołączam obraz, aby zrozumieć moje wymagania.
Czy mógłbyś wyjaśnić, jak można to wdrożyć?
Moja aplikacja wymaga dodania następujących elementów w arkuszu działań.
Dołączam obraz, aby zrozumieć moje wymagania.
Czy mógłbyś wyjaśnić, jak można to wdrożyć?
Odpowiedzi:
Aktualizacja dla iOS 7
Dokumentacja Apple dla UIActionSheet :UIActionSheet is not designed to be subclassed, nor should you add views to its hierarchy
Odradzam dostosowywanie zawartości ActionSheet, ponieważ może to prowadzić do poważnych błędów nieprawidłowego kontekstu w iOS 7. Właśnie spędziłem kilka godzin na pracy nad tym problemem i ostatecznie zdecydowałem się na inne podejście. Zastąpiłem wywołanie pokazania arkusza akcji kontrolerem widoku modalnego zawierającym prosty widok tabeli.
Można to osiągnąć na wiele sposobów. Oto jeden sposób, który właśnie wdrożyłem w bieżącym projekcie. To fajne, ponieważ mogę go ponownie użyć między 5 lub 6 różnymi ekranami, na których wszyscy użytkownicy wybierają z listy opcji.
SimpleTableViewController
.SimpleTableViewControllerDelegate
z wymaganą metodąitemSelectedatRow:
i słabą właściwością o nazwie delegat typu id<SimpleTableViewControllerDelegate>
. W ten sposób przekażemy wybór z powrotem do kontrolera nadrzędnego.itemSelectedatRow:
wtableView:didSelectRowAtIndexPath:
.Takie podejście ma dodatkową zaletę w postaci możliwości ponownego wykorzystania. Aby użyć, zaimportuj klasę SimpleTableViewController w swoim ViewController.h, dostosuj się do SimpleTableViewDelegate i zaimplementuj itemSelectedAtRow:
metodę. Następnie, aby otworzyć modal, po prostu utwórz wystąpienie nowego SimpleTableViewController, ustaw dane tabeli i delegata, a następnie przedstaw je.
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];
Tworzę prosty przykład i umieściłem go na githubie .
Zobacz także Wyświetlanie arkusza działań powoduje błędy w nieprawidłowym kontekście CGContext .
Jeszcze jedno rozwiązanie:
bez paska narzędzi, ale podzielona na segmenty kontrolka (oko)
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)];
Chociaż to pytanie jest stare, szybko wspomnę, że utworzyłem razem klasę ActionSheetPicker z wygodną funkcją, dzięki czemu możesz odrodzić ActionSheet z UIPickerView w jednej linii. Opiera się na kodzie z odpowiedzi na to pytanie.
Edycja: teraz obsługuje również użycie DatePicker i DistancePicker.
ActionSheetDatePicker
trybie możesz dodać wiele przycisków do paska narzędzi u góry. Czy jest to możliwe również z po prostu normalnym ActionSheetStringPicker
?
Tak ! W końcu to znalazłem.
zaimplementuj następujący kod na zdarzeniu kliknięcia przycisku, aby wyświetlić arkusz akcji, jak podano na obrazku pytania.
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)];
Doskonałe rozwiązanie Marcio na to pytanie bardzo mi pomogło w dodawaniu podglądów podrzędnych do arkusza UIActionSheet.
Z powodów, które nie są (jeszcze) całkowicie dla mnie jasne, granice arkusza UIActionSheet można ustawić dopiero po jego wyświetleniu; zarówno sagar, jak i marcio rozwiązują ten problem, wysyłając do arkusza akcji komunikat setBounds: CGRectMake (...) po jego wyświetleniu.
Jednak ustawienie granic UIActionSheet po wyświetleniu arkusza tworzy gwałtowne przejście, gdy arkusz ActionSheet pojawia się, gdzie pojawia się w widoku, a następnie przewija się tylko przez ostatnie 40 pikseli lub więcej.
Podczas określania rozmiaru UIPickerView po dodaniu podglądów podrzędnych, zalecam zawijanie wiadomości setBounds wysyłanej do actionSheet wewnątrz bloku animacji. Dzięki temu wejście actionSheet będzie wyglądać płynniej.
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];
Dla tych facetów, którzy szukają funkcji DatePickerDoneClick ... oto prosty kod, aby zamknąć Arkusz działań. Oczywiście aac powinien być ivar (tym, który znajduje się w twoim pliku implmentacji .h)
- (void)DatePickerDoneClick:(id)sender{
[aac dismissWithClickedButtonIndex:0 animated:YES];
}
Naprawdę nie rozumiem, dlaczego UIPickerView
dzieje się wewnątrz pliku UIActionSheet
. Wydaje się, że jest to niechlujne i hakerskie rozwiązanie, które może zostać zepsute w przyszłej wersji iOS. (Miałem już takie rzeczy jak ta przerwa w aplikacji, w której UIPickerView
nie były wyświetlane przy pierwszym dotknięciu i musiały zostać ponownie zmapowane - dziwne dziwactwa zUIActionSheet
).
To, co zrobiłem, to po prostu zaimplementowałem a, UIPickerView
a następnie dodałem go jako podwidok do mojego widoku i ożywiłem go tak, jakby był prezentowany jak arkusz akcji.
/// 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;
}];
}
Aby dodać do niesamowitego rozwiązania marcio, dismissActionSheet:
można je zaimplementować w następujący sposób.
Dodaj tę metodę do swojego kodu.
- (void)dismissActionSheet:(id)sender{
[_actionSheet dismissWithClickedButtonIndex:0 animated:YES];
[_myButton setTitle:@"new title"]; //set to selected text if wanted
}
Myślę, że to najlepszy sposób na zrobienie tego.
To prawie to, co wszyscy sugerują, ale używa klocków, co jest miłym akcentem!
Od iOS 8 nie możesz, to nie działa, ponieważ Apple zmieniło wewnętrzną implementację UIActionSheet
. Zapoznaj się z dokumentacją Apple :
Uwagi dotyczące podklas
Arkusz UIActionSheet nie jest przeznaczony do podklasy ani nie należy dodawać widoków do jego hierarchii . Jeśli chcesz przedstawić arkusz z większym dostosowaniem niż zapewnia interfejs API UIActionSheet, możesz utworzyć własny i zaprezentować go modalnie za pomocą presentViewController: animated: Complete :.
Podobało mi się podejście przyjęte przez Wayfarera i flexaddicted, ale stwierdziłem (podobnie jak aZtral), że nie działa, ponieważ backgroundTapButton był jedynym elementem, który reagował na interakcję użytkownika. To doprowadziło mnie do umieszczenia wszystkich trzech jego podglądów podrzędnych: _picker, _pickerToolbar i backgroundTapButton wewnątrz widoku zawierającego (popup), który był następnie animowany na ekranie i poza nim. Potrzebowałem również przycisku Anuluj na pasku _pickerToolbar. Oto odpowiednie elementy kodu dla widoku wyskakującego (musisz podać własne źródło danych selektora i metody delegata).
#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;
}