I am programmatically adding a UISegmentedControl to a UINavigationController's toolbar (I am in a UITableViewController). I want the segmented control to look decent, not filling the entire bar. Also, I want it to rotate with the view and resize. This should be pretty easy but I think I'm missing something. I actually got it to work, but this is not clean code so I am hoping someone can tell me which settings/methods to use for a "proper" implementation.
Getter:
- (UISegmentedControl*)stockFilterSegmentedControl {
if (!_stockFilterSegmentedControl) {
_stockFilterSegmentedControl = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:#"All",#"Holdings", nil]];
[_stockFilterSegmentedControl addTarget:self action:#selector(stockFilterControlPressed:) forControlEvents:UIControlEventValueChanged];
_stockFilterSegmentedControl.selectedSegmentIndex = 0;
_stockFilterSegmentedControl.autoresizingMask = UIViewAutoresizingFlexibleHeight;
CGRect newFrame = _stockFilterSegmentedControl.frame;
newFrame.size.height = self.navigationController.toolbar.frame.size.height * .8;
_stockFilterSegmentedControl.frame = newFrame;
}
return _stockFilterSegmentedControl;
}
Where we insert it:
- (NSArray*)navFooterToolbarArray {
UIBarButtonItem *barButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.stockFilterSegmentedControl];
UIBarButtonItem *flexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
UIBarButtonItem *refresh = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:#selector(refresh:)];
return [NSArray arrayWithObjects:flexibleSpace, barButtonItem, flexibleSpace, refresh, nil];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"Stocks";
self.toolbarItems = [self navFooterToolbarArray];
}
And to make it work I had to add:
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
NSLog(#"Did autorotate.");
CGRect newFrame = self.stockFilterSegmentedControl.frame;
newFrame.size.height = self.navigationController.toolbar.frame.size.height * .8;
self.stockFilterSegmentedControl.frame = newFrame;
}
What's the right way to do this?
Thanks,
Damien
The answer is actually really simple - you set the segmentedControlStyle on the segmented control to UISegmentedControlStyleBar and it will resize perfectly without any drama. Autoresizing masks work as expected.
Thanks,
Damien
All you should really need is something like:
_stockFilterSegmentedControl.frame = CGRectInset(self.navigationController.toolbar.bounds, 5, 5); //set to toolbar rect inset by 5 pixels
_stockFilterSegmentedControl.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
You then won't have to do anything when the view rotates, it should just automatically resize to fit because of the autoresizingMask.
Related
In several cases I want to add a toolbar to the top of the iPhone keyboard (as in iPhone Safari when you're navigating form elements, for example).
Currently I am specifying the toolbar's rectangle with constants but because other elements of the interface are in flux - toolbars and nav bars at the top of the screen - every time we make a minor interface change, the toolbar goes out of alignment.
Is there a way to programmatically determine the position of the keyboard in relation to the current view?
As of iOS 3.2 there's a new way to achieve this effect:
UITextFields and UITextViews have an inputAccessoryView property, which you can set to any view, that is automatically displayed above and animated with the keyboard.
Note that the view you use should neither be in the view hierarchy elsewhere, nor should you add it to some superview, this is done for you.
So basically:
In the init method:
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(keyboardWillShow:) name: UIKeyboardWillShowNotification object:nil];
[nc addObserver:self selector:#selector(keyboardWillHide:) name: UIKeyboardWillHideNotification object:nil];
And then have methods referred to above to adjust the position of the bar:
-(void) keyboardWillShow:(NSNotification *) note
{
CGRect r = bar.frame, t;
[[note.userInfo valueForKey:UIKeyboardBoundsUserInfoKey] getValue: &t];
r.origin.y -= t.size.height;
bar.frame = r;
}
Could make it pretty by animating the position change by wrapping it in
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
//...
[UIView commitAnimations];
This is based on the existing answer from tonklon - I'm just adding a code snippet that shows a semi transparent black toolbar on top of the keyboard, together with a "done" button on the right:
UIToolbar *toolbar = [[[UIToolbar alloc] init] autorelease];
[toolbar setBarStyle:UIBarStyleBlackTranslucent];
[toolbar sizeToFit];
UIBarButtonItem *flexButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
UIBarButtonItem *doneButton =[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(resignKeyboard)];
NSArray *itemsArray = [NSArray arrayWithObjects:flexButton, doneButton, nil];
[flexButton release];
[doneButton release];
[toolbar setItems:itemsArray];
[aTextField setInputAccessoryView:toolbar];
and the -resignKeyboard looks like:
-(void)resignKeyboard {
[aTextField resignFirstResponder];
}
If you register for keyboard notifications, ie UIKeyboardWillShowNotification UIKeyboardWillHideNotification, etc, the notification you receive will contain the bounds of the keyboard in the userInfo dict (UIKeyboardBoundsUserInfoKey).
See the UIWindow class reference.
In 3.0 and above you can get the animation duration and curve from the userInfo dictionary of the notifications.
for instance, to animate the size of the view to make room for the keyboard, register for the UIKeyboardWillShowNotification and do something like the following:
- (void)keyboardWillShow:(NSNotification *)notification
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:[[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
[UIView setAnimationDuration:[[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
CGRect frame = self.view.frame;
frame.size.height -= [[[notification userInfo] objectForKey:UIKeyboardBoundsUserInfoKey] CGRectValue].size.height;
self.view.frame = frame;
[UIView commitAnimations];
}
Do a similar animation for UIKeyboardWillHideNotification.
Create this method and call it on ViewWillLoad:
- (void) keyboardToolbarSetup
{
if(self.keyboardToolbar==nil)
{
self.keyboardToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 44)];
UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithTitle:#"Cancel" style:UIBarButtonItemStylePlain target:self action:#selector(anyAction)];
UIBarButtonItem *extraSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithTitle:#"Done" style:UIBarButtonItemStyleDone target:self action:#selector(anyOtherAction)];
NSArray *toolbarButtons = [[NSArray alloc]initWithObjects:cancelButton,extraSpace,doneButton, nil];
[self.keyboardToolbar setItems:toolbarButtons];
self.myTextView.inputAccessoryView=self.keyboardToolbar;
}
}
There's no way (AFAIK) to get the dimensions of the keyboard view. It is however constant, at least in every iPhone version so far.
If you calculate the toolbar position as an offset from the BOTTOM of your view, and take the size of your view into account, then you should not have to worry whether a navbar is present or not.
E.g.
#define KEYBOARD_HEIGHT 240 // example - can't remember the exact size
#define TOOLBAR_HEIGHT 30
toolBarRect.origin.y = viewRect.size.height - KEYBOARD_HEIGHT - TOOLBAR_HEIGHT;
// move toolbar either directly or with an animation
Instead of a define, you could easily create a keyboardHeight function that returns the size based on whether the keyboard is being displayed, and move this toolbar positioning into a separate function that reorganizes your layout.
Also it can depend on where you do this positioning as it's possible the size of your view may change between being loaded and shown based on your navbar setup. I believe the best place to do it would be in viewWillAppear.
I have custom UIBarButton items added to UINavigationBar as rightbarbuttonitems. I add two more items in UIBarButton when device goes to landscape mode. I am getting rotation call and I am correctly removing items from UIBarbuttons array based on device orientation, but my navigationbar item set never gets updates.
If i start with portrait mode, it shows what it suppose to. When I rotate to landscape new items are not added to navigation bar and vice versa extra item won't go when device go to portrait.
I am not sure how I can change navigationbaritems on device rotation. Per me I am doing all things correct.
-(void)setUpOnRotation:(BOOL)toPortrait{
_searchMessage = [[UILabel alloc] initWithFrame:CGRectMake(3.0, 0, 110, 20)];
_searchMessage.numberOfLines = 2;
_searchMessage.textColor = [UIColor blackColor];
_searchMessage.font = [UIFont systemFontOfSize:8.0];
_searchMessage.text = #"";
_searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 15, 130, 25)];
_searchBar.delegate = self;
_searchBar.showsCancelButton = NO;
UIBarButtonItem* doneButton = [BarButtons doneButtonWithTarget:self action:#selector(cancel)];
UIBarButtonItem *searchActivityItem = [[UIBarButtonItem alloc] initWithCustomView:_searchMessage];
UIBarButtonItem *searchItem = [[UIBarButtonItem alloc] initWithCustomView:_searchBar];
UIBarButtonItem *in = [BarButtons nativeButtonNamed:#"In"
title:#"In"
target:self
action:#selector(In)];
UIBarButtonItem *out = [BarButtons nativeButtonNamed:#"Out"
title:#"Out"
target:self
action:#selector(Out)];
NSMutableArray *itemsSet;
itemsSet = [NSMutableArray arrayWithObjects:in,
out,
searchItem,
searchActivityItem,
doneButton, nil];
if (toPortrait) {
if ([itemsSet containsObject:searchItem] || [itemsSet containsObject:searchActivityItem]) {
[itemsSet removeObject:searchActivityItem];
[itemsSet removeObject:searchItem];
}
}
[_searchBar release];
[searchItem release];
[_searchActivity release];
[_searchMessage release];
[searchActivityView release];
self.navigationItem.rightBarButtonItems = itemsSet;
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self setUpOnRotation:UIInterfaceOrientationIsPortrait(toInterfaceOrientation)];
} -(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:NO animated:animated];
[self setUpOnRotation:UIInterfaceOrientationIsPortrait(self.interfaceOrientation)];}
Note: setUpOnRotaion method is called from willAnimateRotationToInterfaceOrientation and viewWillAppear with UIInterfaceOrientationIsPortrait(self.intefaceOrientation) and it does give me correct result. I have debugged.
In willAnimateRotationToInterfaceOrientation change cehck for orientation
if([uidevice orientaiton]statusbarorientation]){
self.naviogationite.rightbarbuttonitems = #[btn1,btn2,btn3];
}
else{
self.naviogationite.rightbarbuttonitems = #[abtn1,abtn2,abnt3];
}
u can do like this
can u do like this
NSMutableArray *itemsSet;
if(!toPortrait){
itemsSet = [NSMutableArray arrayWithObjects:in,
out,
searchItem,
searchActivityItem,
doneButton, nil];
}
if (toPortrait) {
itemsSet = [NSMutableArray arrayWithObjects:in,
out,
doneButton, nil];
}
I have a view controller that is calling a custom UIView via an IBAction.The custom view contains a UIPickerView that slides up from the bottom of the screen and a toolbar with 'cancel' and 'done' buttons above it. The problem is that the view only appears on the screen after being called for the second time. Using breakpoints I can verify that every single line of code is being called both times. Everything seems to be happening the same way each time. Nothing is NIL, and in fact it's like this for the duration that the app is running, not only the first time it's called. You always have to click the button twice to get the view to appear for as long as the app is running.
Admittedly, the code for the custom picker view is not mine. I copied it from someone else's example. I'm not sure if it's the problem or not. I don't see how it could be, but I'm a bit over my head here. This is how I'm calling the view from my view controller.
- (IBAction)statusPickerButtonPressed:(id)sender {
self.scrollPickerView = [[StatusPickerView alloc]init];
[self.navigationController.view addSubview:self.scrollPickerView];
self.scrollPickerView.delegate = self;
self.scrollPickerView.dataSource = self;
}
and here's the custom UIView
#import "StatusPickerView.h"
#interface StatusPickerView ()
#property NSArray *pickerArray;
#property NSInteger selectedRow;
#end
#implementation StatusPickerView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setToolbar];
[self becomeFirstResponder];
float screenWidth = [UIScreen mainScreen].bounds.size.width;
float pickerWidth = screenWidth * 3 / 4;
float xPoint = screenWidth / 2 - pickerWidth / 2;
[self setFrame: CGRectMake(xPoint, 50.0f, pickerWidth, 180.0f)];
self.showsSelectionIndicator = YES;
[self selectRow:3 inComponent:0 animated:YES];
}
return self;
}
-(void)setToolbar
{
_toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
[_toolbar setBarStyle:UIBarStyleDefault];
UIBarButtonItem * btnCancel = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self action:#selector(barbtnPressed:)];
UIBarButtonItem * flexible = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil action:nil];
UIBarButtonItem * btnDone = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self action:#selector(barbtnPressed:)];
[btnCancel setTag:1];
[btnCancel setStyle:UIBarButtonItemStyleBordered];
[btnDone setTag:2];
[btnDone setStyle:UIBarButtonItemStyleBordered];
NSArray * btnArray = [[NSArray alloc] initWithObjects:btnCancel, flexible, btnDone, nil];
[_toolbar setItems:btnArray];
self.inputAccessoryView = _toolbar;
self.inputView = self;
}
-(BOOL)canBecomeFirstResponder
{
return true;
}
-(void)barbtnPressed:(id)sender
{
NSInteger tag = [sender tag];
switch (tag) {
case 1:
{
[self removeFromSuperview];
break;
}
case 2:{
[self removeFromSuperview];
self.selectedRow = [self selectedRowInComponent:0];
[[NSNotificationCenter defaultCenter]postNotificationName:#"user_selected_new_section" object:self];
}
default:
break;
}
}
-(int)giveSelectedRow{
return self.selectedRow;
}
I'm fully prepared to feel foolish here, as the solution is probably obvious, just not obvious to myself.
edit:
I tried using [self.view.window addSubview:self.scrollPickerView]; instead of [self.navigationController.view addSubview:self.scrollPickerView];, and the behavior is exactly the same.
This line:
[self.navigationController.view addSubview:self.scrollPickerView];
should read:
[self.view addSubview:self.scrollPickerView];
You’re calling -init instead of -initWithFrame:, so your view is ending up with the default frame of CGRectZero. Considering that you’re then making a frame of your own, that call might as well pass that in to start out with:
self.scrollPickerView = [[ScrollPickerView alloc] initWithFrame:CGRectZero];
Alternatively, you could keep the current self.scrollPickerView = … code and change the -initWithFrame: to an -init, like this:
- (id)init
{
float screenWidth = [UIScreen mainScreen].bounds.size.width;
float pickerWidth = screenWidth * 3 / 4;
float xPoint = screenWidth / 2 - pickerWidth / 2;
self = [super initWithFrame:CGRectMake(xPoint, 50.0f, pickerWidth, 180.0f)];
if (self) {
[self setToolbar];
[self becomeFirstResponder];
self.showsSelectionIndicator = YES;
[self selectRow:3 inComponent:0 animated:YES];
}
return self;
}
I'm trying to customize my NavigationBar with the help of a toolbar.
I've implemented it programmatically as follows:
UIToolbar* tools = [[UIToolbar alloc] initWithFrame: CGRectMake(0, 0, 100, 44.01)];
and then I added it to my NavigationBar. The problem is that I have this ugly effect on the borders:
I've tried to change the y and the height values, with no results.
Do you have any ideas to avoid this?
Thanks in advance, yassa
I wouldn't do it this way.
You can achieve the same effect by adding a view with 2 buttons to the navigationItem.rightBarButtonItem. it is very simple:
// view that will hold the buttons
UIView* container = [[UIView alloc] init];
// create 1 button and add it to the container
UIButton* button = [[UIButton alloc] init........];
[container addSubview:button];
//create 2 button and add it to the container
button = [[UIButton alloc] init.........];
[container addSubview:button];
// now create a Bar button item
UIBarButtonItem* barButtonItem = [[UIBarButtonItem alloc] initWithCustomView:container];
// set the nav bar's right button item
self.navigationItem.rightBarButtonItem = barButtonItem;
I partially agree with previous answers and comments.
The solution you suggested works fine for custom buttons. But what if I want to implement standard Edit button?
Access to the standard buttons/icons is through the UIBarButtonItem class, not UIButton. And you can't add UIBarButtonItem objects to a UIView.
After many research on the web, I've found the solution that completely cover my requirement. The toolbar must be created in the following way:
UIToolbar *tools = [[UIToolbar alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 95.0f, 44.01f)];
tools.tintColor = self.navigationController.navigationBar.tintColor;
tools.barStyle = -1;
And this is the result:
Hope it helps!
yassa
Or you can do it in this way.
Just create new subclass of UIToolbar like this
#interface MyToolbar : UIToolbar
#end
#implementation MyToolbar
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
self.opaque = NO;
self.backgroundColor = [UIColor clearColor];
self.clearsContextBeforeDrawing = YES;
}
return self;
}
- (void)drawRect:(CGRect)rect {
// do nothing
}
- (void)dealloc {
[super dealloc];
}
#end
And use it as normal UIToolbar. I don't know why but it just works.
In several cases I want to add a toolbar to the top of the iPhone keyboard (as in iPhone Safari when you're navigating form elements, for example).
Currently I am specifying the toolbar's rectangle with constants but because other elements of the interface are in flux - toolbars and nav bars at the top of the screen - every time we make a minor interface change, the toolbar goes out of alignment.
Is there a way to programmatically determine the position of the keyboard in relation to the current view?
As of iOS 3.2 there's a new way to achieve this effect:
UITextFields and UITextViews have an inputAccessoryView property, which you can set to any view, that is automatically displayed above and animated with the keyboard.
Note that the view you use should neither be in the view hierarchy elsewhere, nor should you add it to some superview, this is done for you.
So basically:
In the init method:
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(keyboardWillShow:) name: UIKeyboardWillShowNotification object:nil];
[nc addObserver:self selector:#selector(keyboardWillHide:) name: UIKeyboardWillHideNotification object:nil];
And then have methods referred to above to adjust the position of the bar:
-(void) keyboardWillShow:(NSNotification *) note
{
CGRect r = bar.frame, t;
[[note.userInfo valueForKey:UIKeyboardBoundsUserInfoKey] getValue: &t];
r.origin.y -= t.size.height;
bar.frame = r;
}
Could make it pretty by animating the position change by wrapping it in
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
//...
[UIView commitAnimations];
This is based on the existing answer from tonklon - I'm just adding a code snippet that shows a semi transparent black toolbar on top of the keyboard, together with a "done" button on the right:
UIToolbar *toolbar = [[[UIToolbar alloc] init] autorelease];
[toolbar setBarStyle:UIBarStyleBlackTranslucent];
[toolbar sizeToFit];
UIBarButtonItem *flexButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
UIBarButtonItem *doneButton =[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(resignKeyboard)];
NSArray *itemsArray = [NSArray arrayWithObjects:flexButton, doneButton, nil];
[flexButton release];
[doneButton release];
[toolbar setItems:itemsArray];
[aTextField setInputAccessoryView:toolbar];
and the -resignKeyboard looks like:
-(void)resignKeyboard {
[aTextField resignFirstResponder];
}
If you register for keyboard notifications, ie UIKeyboardWillShowNotification UIKeyboardWillHideNotification, etc, the notification you receive will contain the bounds of the keyboard in the userInfo dict (UIKeyboardBoundsUserInfoKey).
See the UIWindow class reference.
In 3.0 and above you can get the animation duration and curve from the userInfo dictionary of the notifications.
for instance, to animate the size of the view to make room for the keyboard, register for the UIKeyboardWillShowNotification and do something like the following:
- (void)keyboardWillShow:(NSNotification *)notification
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:[[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
[UIView setAnimationDuration:[[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
CGRect frame = self.view.frame;
frame.size.height -= [[[notification userInfo] objectForKey:UIKeyboardBoundsUserInfoKey] CGRectValue].size.height;
self.view.frame = frame;
[UIView commitAnimations];
}
Do a similar animation for UIKeyboardWillHideNotification.
Create this method and call it on ViewWillLoad:
- (void) keyboardToolbarSetup
{
if(self.keyboardToolbar==nil)
{
self.keyboardToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 44)];
UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithTitle:#"Cancel" style:UIBarButtonItemStylePlain target:self action:#selector(anyAction)];
UIBarButtonItem *extraSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithTitle:#"Done" style:UIBarButtonItemStyleDone target:self action:#selector(anyOtherAction)];
NSArray *toolbarButtons = [[NSArray alloc]initWithObjects:cancelButton,extraSpace,doneButton, nil];
[self.keyboardToolbar setItems:toolbarButtons];
self.myTextView.inputAccessoryView=self.keyboardToolbar;
}
}
There's no way (AFAIK) to get the dimensions of the keyboard view. It is however constant, at least in every iPhone version so far.
If you calculate the toolbar position as an offset from the BOTTOM of your view, and take the size of your view into account, then you should not have to worry whether a navbar is present or not.
E.g.
#define KEYBOARD_HEIGHT 240 // example - can't remember the exact size
#define TOOLBAR_HEIGHT 30
toolBarRect.origin.y = viewRect.size.height - KEYBOARD_HEIGHT - TOOLBAR_HEIGHT;
// move toolbar either directly or with an animation
Instead of a define, you could easily create a keyboardHeight function that returns the size based on whether the keyboard is being displayed, and move this toolbar positioning into a separate function that reorganizes your layout.
Also it can depend on where you do this positioning as it's possible the size of your view may change between being loaded and shown based on your navbar setup. I believe the best place to do it would be in viewWillAppear.